fix(ci): fix E2E job env to unblock every PR — GOO-136
Root cause: the ci.yml e2e job had JWT_SECRET (23 chars) and
JWT_REFRESH_SECRET (27 chars), both below the MIN_SECRET_LENGTH=32
enforced by env-validation.ts. The API webServer refused to start with
"Insecure JWT secret configuration", so every E2E test failed with
ERR_CONNECTION_REFUSED before a single assertion ran.
Fixes in this commit:
1. .github/workflows/ci.yml — sync e2e job env with the working
standalone e2e.yml:
• JWT secrets lengthened past 32-char minimum
• Add API_PORT / WEB_PORT / *_BASE_URL for Playwright webServer
• Add REDIS_HOST/PORT, TYPESENSE_PROTOCOL, JWT_EXPIRES_IN,
BCRYPT_ROUNDS, GOOGLE_*/ZALO_* OAuth stubs
• VNPAY_HASH_SECRET lengthened to 32 chars
• MINIO creds use vars fallback pattern (matches e2e.yml)
2. phone-login-otp-requested.event.ts + .listener.ts — the notifications
module imported PhoneLoginOtpRequestedListener but neither the event
class nor the listener file existed, causing module-resolution errors
in four unrelated test suites that import notifications.module.ts.
3. create-payment.handler.spec.ts — inject mockSbvCompliance so the two
handler tests that were TypeError-ing on undefined.error() pass.
Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
33
.github/workflows/ci.yml
vendored
33
.github/workflows/ci.yml
vendored
@@ -195,8 +195,8 @@ jobs:
|
||||
ports:
|
||||
- 9000:9000
|
||||
env:
|
||||
MINIO_ROOT_USER: ci_minio_user
|
||||
MINIO_ROOT_PASSWORD: ci_minio_secret_key_32chars!!
|
||||
MINIO_ROOT_USER: ${{ vars.CI_MINIO_ACCESS_KEY || 'ci_minio_user' }}
|
||||
MINIO_ROOT_PASSWORD: ${{ vars.CI_MINIO_SECRET_KEY || 'ci_minio_secret_key_32chars!!' }}
|
||||
options: >-
|
||||
--health-cmd "curl -sf http://localhost:9000/minio/health/live || exit 1"
|
||||
--health-interval 10s
|
||||
@@ -206,22 +206,41 @@ jobs:
|
||||
env:
|
||||
DATABASE_URL: postgresql://goodgo:goodgo_test_secret@localhost:5432/goodgo_test
|
||||
REDIS_URL: redis://localhost:6379
|
||||
REDIS_HOST: localhost
|
||||
REDIS_PORT: 6379
|
||||
TYPESENSE_URL: http://localhost:8108
|
||||
TYPESENSE_HOST: localhost
|
||||
TYPESENSE_PORT: 8108
|
||||
TYPESENSE_PROTOCOL: http
|
||||
TYPESENSE_API_KEY: ts_ci_key
|
||||
MINIO_ENDPOINT: localhost
|
||||
MINIO_PORT: 9000
|
||||
MINIO_ACCESS_KEY: ci_minio_user
|
||||
MINIO_SECRET_KEY: ci_minio_secret_key_32chars!!
|
||||
MINIO_ACCESS_KEY: ${{ vars.CI_MINIO_ACCESS_KEY || 'ci_minio_user' }}
|
||||
MINIO_SECRET_KEY: ${{ vars.CI_MINIO_SECRET_KEY || 'ci_minio_secret_key_32chars!!' }}
|
||||
MINIO_BUCKET: goodgo-uploads
|
||||
NODE_ENV: test
|
||||
JWT_SECRET: e2e-test-jwt-secret-key
|
||||
JWT_REFRESH_SECRET: e2e-test-refresh-secret-key
|
||||
CI: true
|
||||
# API and Web ports for Playwright webServer
|
||||
API_PORT: 3001
|
||||
WEB_PORT: 3000
|
||||
API_BASE_URL: http://localhost:3001/api/v1/
|
||||
WEB_BASE_URL: http://localhost:3000
|
||||
NEXT_PUBLIC_API_URL: http://localhost:3001/api/v1
|
||||
JWT_SECRET: e2e-test-jwt-secret-key-minimum-32-chars-long-enough
|
||||
JWT_REFRESH_SECRET: e2e-test-refresh-secret-key-minimum-32-chars-ok
|
||||
JWT_EXPIRES_IN: 15m
|
||||
JWT_REFRESH_EXPIRES_IN: 7d
|
||||
BCRYPT_ROUNDS: 4
|
||||
VNPAY_TMN_CODE: TESTCODE
|
||||
VNPAY_HASH_SECRET: TESTHASHSECRET
|
||||
VNPAY_HASH_SECRET: TESTHASHSECRETTESTHASHSECRETTEST
|
||||
VNPAY_URL: https://sandbox.vnpayment.vn/paymentv2/vpcpay.html
|
||||
VNPAY_RETURN_URL: http://localhost:3000/payment/return
|
||||
GOOGLE_CLIENT_ID: test-google-client-id
|
||||
GOOGLE_CLIENT_SECRET: test-google-client-secret
|
||||
GOOGLE_CALLBACK_URL: http://localhost:3001/api/v1/auth/google/callback
|
||||
ZALO_APP_ID: test-zalo-app-id
|
||||
ZALO_APP_SECRET: test-zalo-app-secret
|
||||
ZALO_CALLBACK_URL: http://localhost:3001/api/v1/auth/zalo/callback
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
import { type DomainEvent } from '@modules/shared';
|
||||
|
||||
export class PhoneLoginOtpRequestedEvent implements DomainEvent {
|
||||
readonly eventName = 'user.phone_login_otp';
|
||||
readonly occurredAt = new Date();
|
||||
|
||||
constructor(
|
||||
public readonly aggregateId: string,
|
||||
public readonly phone: string,
|
||||
public readonly otpCode: string,
|
||||
) {}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { CommandBus } from '@nestjs/cqrs';
|
||||
import { OnEvent } from '@nestjs/event-emitter';
|
||||
import { type PhoneLoginOtpRequestedEvent } from '@modules/auth/domain/events/phone-login-otp-requested.event';
|
||||
import { LoggerService } from '@modules/shared';
|
||||
import { SendNotificationCommand } from '../commands/send-notification/send-notification.command';
|
||||
|
||||
@Injectable()
|
||||
export class PhoneLoginOtpRequestedListener {
|
||||
constructor(
|
||||
private readonly commandBus: CommandBus,
|
||||
private readonly logger: LoggerService,
|
||||
) {}
|
||||
|
||||
@OnEvent('user.phone_login_otp', { async: true })
|
||||
async handle(event: PhoneLoginOtpRequestedEvent): Promise<void> {
|
||||
this.logger.log(
|
||||
`Handling phone login OTP for user ${event.aggregateId}`,
|
||||
'PhoneLoginOtpRequestedListener',
|
||||
);
|
||||
|
||||
await this.commandBus.execute(
|
||||
new SendNotificationCommand(
|
||||
event.aggregateId,
|
||||
'SMS',
|
||||
'user.phone_login_otp',
|
||||
{ otpCode: event.otpCode },
|
||||
undefined,
|
||||
event.phone,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -8,6 +8,7 @@ describe('CreatePaymentHandler', () => {
|
||||
let mockGatewayFactory: { getGateway: ReturnType<typeof vi.fn> };
|
||||
let mockGateway: { createPaymentUrl: ReturnType<typeof vi.fn>; verifyCallback: ReturnType<typeof vi.fn>; refund: ReturnType<typeof vi.fn> };
|
||||
let mockEventBus: { publish: ReturnType<typeof vi.fn> };
|
||||
let mockSbvCompliance: { checkPaymentCompliance: ReturnType<typeof vi.fn> };
|
||||
|
||||
beforeEach(() => {
|
||||
mockPaymentRepo = {
|
||||
@@ -35,11 +36,16 @@ describe('CreatePaymentHandler', () => {
|
||||
|
||||
mockEventBus = { publish: vi.fn() };
|
||||
|
||||
mockSbvCompliance = {
|
||||
checkPaymentCompliance: vi.fn().mockReturnValue({ allowed: true, sbvLargeTransaction: false, amlFlagged: false }),
|
||||
};
|
||||
|
||||
const mockLogger = { log: vi.fn(), warn: vi.fn(), error: vi.fn(), debug: vi.fn(), verbose: vi.fn() };
|
||||
|
||||
handler = new CreatePaymentHandler(
|
||||
mockPaymentRepo as any,
|
||||
mockGatewayFactory as any,
|
||||
mockSbvCompliance as any,
|
||||
mockEventBus as any,
|
||||
mockLogger as any,
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user