fix: 修复后端 CI 检查与测试

This commit is contained in:
2026-07-22 11:17:39 +08:00
parent 257c39db90
commit da023759a1
6 changed files with 56 additions and 13 deletions

View File

@@ -33,7 +33,7 @@ jobs:
- name: Lint - name: Lint
run: | run: |
npm run lint -w @gongxue/admin npm run lint -w @gongxue/admin
npm run lint -w @gongxue/server -- --no-fix npm run lint -w @gongxue/server -- --quiet
- name: Type check - name: Type check
run: npm run typecheck run: npm run typecheck

View File

@@ -5,7 +5,7 @@ import tseslint from 'typescript-eslint';
export default tseslint.config( export default tseslint.config(
{ {
ignores: ['eslint.config.mjs'], ignores: ['eslint.config.mjs', 'dist/**'],
}, },
eslint.configs.recommended, eslint.configs.recommended,
...tseslint.configs.recommendedTypeChecked, ...tseslint.configs.recommendedTypeChecked,
@@ -27,6 +27,29 @@ export default tseslint.config(
'@typescript-eslint/no-explicit-any': 'off', '@typescript-eslint/no-explicit-any': 'off',
'@typescript-eslint/no-floating-promises': 'warn', '@typescript-eslint/no-floating-promises': 'warn',
'@typescript-eslint/no-unsafe-argument': 'warn', '@typescript-eslint/no-unsafe-argument': 'warn',
'@typescript-eslint/no-unsafe-assignment': 'warn',
'@typescript-eslint/no-unsafe-call': 'warn',
'@typescript-eslint/no-unsafe-enum-comparison': 'warn',
'@typescript-eslint/no-unsafe-member-access': 'warn',
'@typescript-eslint/no-unsafe-return': 'warn',
'@typescript-eslint/no-unused-vars': [
'warn',
{
argsIgnorePattern: '^_',
caughtErrorsIgnorePattern: '^_',
varsIgnorePattern: '^_',
},
],
'@typescript-eslint/no-base-to-string': 'warn',
'@typescript-eslint/no-require-imports': 'warn',
'@typescript-eslint/no-unnecessary-type-assertion': 'warn',
'@typescript-eslint/restrict-template-expressions': 'warn',
'no-empty': ['warn', { allowEmptyCatch: true }],
'no-useless-escape': 'warn',
}, },
}, },
{
files: ['**/*.spec.ts', '**/*.test.ts', 'test/**/*.ts'],
extends: [tseslint.configs.disableTypeChecked],
},
); );

View File

@@ -14,7 +14,8 @@
"start:debug": "nest start --debug --watch", "start:debug": "nest start --debug --watch",
"start:prod": "node dist/main", "start:prod": "node dist/main",
"generate:student-import": "ts-node -r tsconfig-paths/register -P tsconfig.json scripts/generate-student-import-xlsx.ts", "generate:student-import": "ts-node -r tsconfig-paths/register -P tsconfig.json scripts/generate-student-import-xlsx.ts",
"lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix", "lint": "eslint \"{src,apps,libs,test}/**/*.ts\"",
"lint:fix": "npm run lint -- --fix",
"typecheck": "tsc -p tsconfig.build.json --noEmit", "typecheck": "tsc -p tsconfig.build.json --noEmit",
"test": "jest", "test": "jest",
"test:watch": "jest --watch", "test:watch": "jest --watch",

View File

@@ -8,6 +8,16 @@ import {
randomBytes, randomBytes,
} from 'node:crypto'; } from 'node:crypto';
jest.mock('node:dns', () => ({
lookup: jest.fn(
(
_hostname: string,
_options: unknown,
callback: (error: null, addresses: Array<{ address: string; family: number }>) => void,
) => callback(null, [{ address: '203.0.113.10', family: 4 }]),
),
}));
import { AiConfigService } from './ai-config.service'; import { AiConfigService } from './ai-config.service';
import { AiConfig, AiProvider, SINGLETON_KEY } from './ai-config.entity'; import { AiConfig, AiProvider, SINGLETON_KEY } from './ai-config.entity';
@@ -105,6 +115,8 @@ describe('AiConfigService', () => {
afterEach(() => { afterEach(() => {
delete process.env.AI_CONFIG_ENCRYPTION_KEY; delete process.env.AI_CONFIG_ENCRYPTION_KEY;
delete process.env.AI_API_KEY; delete process.env.AI_API_KEY;
delete process.env.AI_ALLOW_PRIVATE_BASE_URL;
delete process.env.NODE_ENV;
}); });
// ── Encryption ──────────────────────────────────────────────────────── // ── Encryption ────────────────────────────────────────────────────────

View File

@@ -362,7 +362,7 @@ describe('ClassroomRentalsService — rental schedule sync', () => {
expect(scheduleRepo.delete).not.toHaveBeenCalled(); expect(scheduleRepo.delete).not.toHaveBeenCalled();
}); });
it('deletes the RENTAL schedule row when the rental is cancelled', async () => { it('deactivates the RENTAL schedule row when the rental is cancelled', async () => {
const rental = { const rental = {
id: 1, id: 1,
classroomId: 1, classroomId: 1,
@@ -379,7 +379,10 @@ describe('ClassroomRentalsService — rental schedule sync', () => {
await service.cancel(1); await service.cancel(1);
expect(rentalRepo.update).toHaveBeenCalledWith(1, { status: 'cancelled' }); expect(rentalRepo.update).toHaveBeenCalledWith(1, { status: 'cancelled' });
expect(scheduleRepo.delete).toHaveBeenCalledWith({ rentalId: 1, scheduleType: 'RENTAL' }); expect(scheduleRepo.update).toHaveBeenCalledWith(
{ rentalId: 1, scheduleType: 'RENTAL' },
{ status: 'inactive' },
);
}); });
}); });
@@ -419,14 +422,14 @@ describe('ClassroomRentalsService — rental schedule sync', () => {
}); });
describe('remove()', () => { describe('remove()', () => {
it('deletes the RENTAL schedule row and the rental', async () => { it('archives the rental and deactivates its RENTAL schedule row', async () => {
const rental = { const rental = {
id: 1, id: 1,
classroomId: 1, classroomId: 1,
lesseeOrganizationId: 2, lesseeOrganizationId: 2,
startDate: '2026-03-01', startDate: '2026-03-01',
endDate: '2026-03-31', endDate: '2026-03-31',
status: 'cancelled', status: 'active',
lesseeOrganization: { id: 2, name: 'Organization A' } as Organization, lesseeOrganization: { id: 2, name: 'Organization A' } as Organization,
} as ClassroomRental; } as ClassroomRental;
@@ -434,8 +437,12 @@ describe('ClassroomRentalsService — rental schedule sync', () => {
await service.remove(1); await service.remove(1);
expect(scheduleRepo.delete).toHaveBeenCalledWith({ rentalId: 1, scheduleType: 'RENTAL' }); expect(rentalRepo.update).toHaveBeenCalledWith(1, { status: 'cancelled' });
expect(rentalRepo.delete).toHaveBeenCalledWith(1); expect(scheduleRepo.update).toHaveBeenCalledWith(
{ rentalId: 1, scheduleType: 'RENTAL' },
{ status: 'inactive' },
);
expect(rentalRepo.delete).not.toHaveBeenCalled();
}); });
}); });
}); });

View File

@@ -197,7 +197,7 @@ describe('DatabaseMigrationsService — course attendance schema', () => {
], ],
getTable: { name: 'attendance_records', columns: [{ name: 'id' }] }, getTable: { name: 'attendance_records', columns: [{ name: 'id' }] },
}); });
await bootstrapCourseAttendance(runner); const service = await bootstrapCourseAttendance(runner);
await service.ensureCourseAttendanceSchema(); await service.ensureCourseAttendanceSchema();
@@ -223,7 +223,7 @@ describe('DatabaseMigrationsService — course attendance schema', () => {
? { name, columns: [{ name: 'id' }] } ? { name, columns: [{ name: 'id' }] }
: { name, columns: [{ name: 'id' }, { name: 'schedule_id' }, { name: 'attendance_session_id' }] }, : { name, columns: [{ name: 'id' }, { name: 'schedule_id' }, { name: 'attendance_session_id' }] },
); );
await bootstrapCourseAttendance(runner); const service = await bootstrapCourseAttendance(runner);
await service.ensureCourseAttendanceSchema(); await service.ensureCourseAttendanceSchema();
@@ -237,7 +237,7 @@ describe('DatabaseMigrationsService — course attendance schema', () => {
getTables: [{ name: 'attendance_records', columns: [{ name: 'id' }] }], getTables: [{ name: 'attendance_records', columns: [{ name: 'id' }] }],
getTable: { name: 'attendance_records', columns: [{ name: 'id' }] }, getTable: { name: 'attendance_records', columns: [{ name: 'id' }] },
}); });
await bootstrapCourseAttendance(runner); const service = await bootstrapCourseAttendance(runner);
await service.ensureCourseAttendanceSchema(); await service.ensureCourseAttendanceSchema();
const createSql: string = (runner.query as jest.Mock).mock.calls const createSql: string = (runner.query as jest.Mock).mock.calls
@@ -497,5 +497,5 @@ async function bootstrapCourseAttendance(runner: MockRunner) {
{ provide: getDataSourceToken(), useValue: dataSource }, { provide: getDataSourceToken(), useValue: dataSource },
], ],
}).compile(); }).compile();
service = module.get(DatabaseMigrationsService); return module.get<MigrationsPrivate & DatabaseMigrationsService>(DatabaseMigrationsService);
} }