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:
|
ports:
|
||||||
- 9000:9000
|
- 9000:9000
|
||||||
env:
|
env:
|
||||||
MINIO_ROOT_USER: ci_minio_user
|
MINIO_ROOT_USER: ${{ vars.CI_MINIO_ACCESS_KEY || 'ci_minio_user' }}
|
||||||
MINIO_ROOT_PASSWORD: ci_minio_secret_key_32chars!!
|
MINIO_ROOT_PASSWORD: ${{ vars.CI_MINIO_SECRET_KEY || 'ci_minio_secret_key_32chars!!' }}
|
||||||
options: >-
|
options: >-
|
||||||
--health-cmd "curl -sf http://localhost:9000/minio/health/live || exit 1"
|
--health-cmd "curl -sf http://localhost:9000/minio/health/live || exit 1"
|
||||||
--health-interval 10s
|
--health-interval 10s
|
||||||
@@ -206,22 +206,41 @@ jobs:
|
|||||||
env:
|
env:
|
||||||
DATABASE_URL: postgresql://goodgo:goodgo_test_secret@localhost:5432/goodgo_test
|
DATABASE_URL: postgresql://goodgo:goodgo_test_secret@localhost:5432/goodgo_test
|
||||||
REDIS_URL: redis://localhost:6379
|
REDIS_URL: redis://localhost:6379
|
||||||
|
REDIS_HOST: localhost
|
||||||
|
REDIS_PORT: 6379
|
||||||
TYPESENSE_URL: http://localhost:8108
|
TYPESENSE_URL: http://localhost:8108
|
||||||
TYPESENSE_HOST: localhost
|
TYPESENSE_HOST: localhost
|
||||||
TYPESENSE_PORT: 8108
|
TYPESENSE_PORT: 8108
|
||||||
|
TYPESENSE_PROTOCOL: http
|
||||||
TYPESENSE_API_KEY: ts_ci_key
|
TYPESENSE_API_KEY: ts_ci_key
|
||||||
MINIO_ENDPOINT: localhost
|
MINIO_ENDPOINT: localhost
|
||||||
MINIO_PORT: 9000
|
MINIO_PORT: 9000
|
||||||
MINIO_ACCESS_KEY: ci_minio_user
|
MINIO_ACCESS_KEY: ${{ vars.CI_MINIO_ACCESS_KEY || 'ci_minio_user' }}
|
||||||
MINIO_SECRET_KEY: ci_minio_secret_key_32chars!!
|
MINIO_SECRET_KEY: ${{ vars.CI_MINIO_SECRET_KEY || 'ci_minio_secret_key_32chars!!' }}
|
||||||
MINIO_BUCKET: goodgo-uploads
|
MINIO_BUCKET: goodgo-uploads
|
||||||
NODE_ENV: test
|
NODE_ENV: test
|
||||||
JWT_SECRET: e2e-test-jwt-secret-key
|
CI: true
|
||||||
JWT_REFRESH_SECRET: e2e-test-refresh-secret-key
|
# 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_TMN_CODE: TESTCODE
|
||||||
VNPAY_HASH_SECRET: TESTHASHSECRET
|
VNPAY_HASH_SECRET: TESTHASHSECRETTESTHASHSECRETTEST
|
||||||
VNPAY_URL: https://sandbox.vnpayment.vn/paymentv2/vpcpay.html
|
VNPAY_URL: https://sandbox.vnpayment.vn/paymentv2/vpcpay.html
|
||||||
VNPAY_RETURN_URL: http://localhost:3000/payment/return
|
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:
|
steps:
|
||||||
- name: Checkout
|
- 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 mockGatewayFactory: { getGateway: ReturnType<typeof vi.fn> };
|
||||||
let mockGateway: { createPaymentUrl: ReturnType<typeof vi.fn>; verifyCallback: ReturnType<typeof vi.fn>; refund: 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 mockEventBus: { publish: ReturnType<typeof vi.fn> };
|
||||||
|
let mockSbvCompliance: { checkPaymentCompliance: ReturnType<typeof vi.fn> };
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
mockPaymentRepo = {
|
mockPaymentRepo = {
|
||||||
@@ -35,11 +36,16 @@ describe('CreatePaymentHandler', () => {
|
|||||||
|
|
||||||
mockEventBus = { publish: vi.fn() };
|
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() };
|
const mockLogger = { log: vi.fn(), warn: vi.fn(), error: vi.fn(), debug: vi.fn(), verbose: vi.fn() };
|
||||||
|
|
||||||
handler = new CreatePaymentHandler(
|
handler = new CreatePaymentHandler(
|
||||||
mockPaymentRepo as any,
|
mockPaymentRepo as any,
|
||||||
mockGatewayFactory as any,
|
mockGatewayFactory as any,
|
||||||
|
mockSbvCompliance as any,
|
||||||
mockEventBus as any,
|
mockEventBus as any,
|
||||||
mockLogger as any,
|
mockLogger as any,
|
||||||
);
|
);
|
||||||
|
|||||||
Reference in New Issue
Block a user