generated from ttipo/project-base
feat(backend): AI 코스 생성 API 골격과 생성 기록 테이블 추가 #13
@@ -54,15 +54,24 @@ export class CourseGenerationRunner
|
||||
private readonly pipeline: CourseGenerationPipeline,
|
||||
) {}
|
||||
|
||||
/** 서버가 한 대라 기동할 때 남아 있는 작업은 모두 지난 프로세스의 것이다 */
|
||||
/**
|
||||
* 서버가 한 대라 기동할 때 남아 있는 작업은 모두 지난 프로세스의 것이다.
|
||||
* 정리일 뿐이라 실패해도 기동은 막지 않는다 — 마이그레이션 전이라 테이블이 없을 때 서버 전체가 뜨지 않았다(2026-11-11)
|
||||
*/
|
||||
async onApplicationBootstrap(): Promise<void> {
|
||||
const result = await this.generations.update(
|
||||
{ status: In([...ACTIVE_STATUSES]) },
|
||||
this.failedValues(),
|
||||
);
|
||||
if (result.affected) {
|
||||
this.logger.warn(
|
||||
`지난 실행에서 끝나지 못한 코스 생성 ${result.affected}건을 실패로 바꿨다`,
|
||||
try {
|
||||
const result = await this.generations.update(
|
||||
{ status: In([...ACTIVE_STATUSES]) },
|
||||
this.failedValues(),
|
||||
);
|
||||
if (result.affected) {
|
||||
this.logger.warn(
|
||||
`지난 실행에서 끝나지 못한 코스 생성 ${result.affected}건을 실패로 바꿨다`,
|
||||
);
|
||||
}
|
||||
} catch (error) {
|
||||
this.logger.error(
|
||||
`[SYS_001] 끝나지 못한 코스 생성을 정리하지 못했다: ${error instanceof Error ? error.message : String(error)}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -171,6 +171,9 @@ describe('AI 코스 생성 API (e2e)', () => {
|
||||
}),
|
||||
})
|
||||
.compile();
|
||||
// 운영처럼 마이그레이션을 기동 훅보다 먼저 돌린다 — 빈 DB 에서 실행기의 기동 정리가 테이블을 찾는다
|
||||
ds = moduleFixture.get(DataSource);
|
||||
await ds.runMigrations();
|
||||
app = moduleFixture.createNestApplication();
|
||||
app.setGlobalPrefix('api');
|
||||
app.use(new RequestIdMiddleware().use.bind(new RequestIdMiddleware()));
|
||||
@@ -184,9 +187,6 @@ describe('AI 코스 생성 API (e2e)', () => {
|
||||
app.useGlobalFilters(new HttpExceptionFilter());
|
||||
app.useGlobalInterceptors(new TransformInterceptor(app.get(Reflector)));
|
||||
await app.init();
|
||||
|
||||
ds = app.get(DataSource);
|
||||
await ds.runMigrations();
|
||||
await cleanup();
|
||||
|
||||
const places = ds.getRepository(Place);
|
||||
@@ -216,8 +216,12 @@ describe('AI 코스 생성 API (e2e)', () => {
|
||||
});
|
||||
|
||||
afterAll(async () => {
|
||||
await cleanup();
|
||||
await app.close();
|
||||
// 준비가 실패해도 앱은 닫는다 — 닫지 않으면 주기 작업이 남아 jest 가 끝나지 않는다
|
||||
try {
|
||||
await cleanup();
|
||||
} finally {
|
||||
await app.close();
|
||||
}
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
|
||||
@@ -66,6 +66,9 @@ describe('축제·행사 조회 API (e2e)', () => {
|
||||
const moduleFixture: TestingModule = await Test.createTestingModule({
|
||||
imports: [AppModule],
|
||||
}).compile();
|
||||
// 운영처럼 마이그레이션을 기동 훅보다 먼저 돌린다 — 빈 DB 에서 기동 훅(코스 생성 정리)이 테이블을 찾는다
|
||||
ds = moduleFixture.get(DataSource);
|
||||
await ds.runMigrations();
|
||||
app = moduleFixture.createNestApplication();
|
||||
app.setGlobalPrefix('api');
|
||||
app.use(new RequestIdMiddleware().use.bind(new RequestIdMiddleware()));
|
||||
@@ -79,9 +82,6 @@ describe('축제·행사 조회 API (e2e)', () => {
|
||||
app.useGlobalFilters(new HttpExceptionFilter());
|
||||
app.useGlobalInterceptors(new TransformInterceptor(app.get(Reflector)));
|
||||
await app.init();
|
||||
|
||||
ds = app.get(DataSource);
|
||||
await ds.runMigrations();
|
||||
await cleanup();
|
||||
|
||||
const repo = ds.getRepository(FestivalEvent);
|
||||
@@ -156,8 +156,12 @@ describe('축제·행사 조회 API (e2e)', () => {
|
||||
});
|
||||
|
||||
afterAll(async () => {
|
||||
await cleanup();
|
||||
await app.close();
|
||||
// 준비가 실패해도 앱은 닫는다 — 닫지 않으면 주기 작업이 남아 jest 가 끝나지 않는다
|
||||
try {
|
||||
await cleanup();
|
||||
} finally {
|
||||
await app.close();
|
||||
}
|
||||
});
|
||||
|
||||
const get = (path: string) => request(app.getHttpServer()).get(path);
|
||||
|
||||
@@ -83,6 +83,9 @@ describe('장소 조회 API (e2e)', () => {
|
||||
const moduleFixture: TestingModule = await Test.createTestingModule({
|
||||
imports: [AppModule],
|
||||
}).compile();
|
||||
// 운영처럼 마이그레이션을 기동 훅보다 먼저 돌린다 — 빈 DB 에서 기동 훅(코스 생성 정리)이 테이블을 찾는다
|
||||
ds = moduleFixture.get(DataSource);
|
||||
await ds.runMigrations();
|
||||
app = moduleFixture.createNestApplication();
|
||||
app.setGlobalPrefix('api');
|
||||
app.use(new RequestIdMiddleware().use.bind(new RequestIdMiddleware()));
|
||||
@@ -96,9 +99,6 @@ describe('장소 조회 API (e2e)', () => {
|
||||
app.useGlobalFilters(new HttpExceptionFilter());
|
||||
app.useGlobalInterceptors(new TransformInterceptor(app.get(Reflector)));
|
||||
await app.init();
|
||||
|
||||
ds = app.get(DataSource);
|
||||
await ds.runMigrations();
|
||||
await cleanup();
|
||||
|
||||
const places = ds.getRepository(Place);
|
||||
@@ -245,8 +245,12 @@ describe('장소 조회 API (e2e)', () => {
|
||||
});
|
||||
|
||||
afterAll(async () => {
|
||||
await cleanup();
|
||||
await app.close();
|
||||
// 준비가 실패해도 앱은 닫는다 — 닫지 않으면 주기 작업이 남아 jest 가 끝나지 않는다
|
||||
try {
|
||||
await cleanup();
|
||||
} finally {
|
||||
await app.close();
|
||||
}
|
||||
});
|
||||
|
||||
const get = (path: string) => request(app.getHttpServer()).get(path);
|
||||
|
||||
Reference in New Issue
Block a user