Skip to content

Commit 1d9ff0b

Browse files
committed
feat(ingest): migrate forest fire info source to new public API
1 parent d0f3985 commit 1d9ff0b

2 files changed

Lines changed: 317 additions & 108 deletions

File tree

src/modules/ingest/app/sources/forest-fire-info.source.test.ts

Lines changed: 204 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -13,15 +13,16 @@ describe('ForestFireInfoSource', () => {
1313
vi.setSystemTime(new Date('2025-01-02T00:00:00.000Z'));
1414

1515
const responseBody = {
16-
frfrInfoList: [
16+
fireShowInfoList: [
1717
{
18-
frfr_info_id: 'fire-1',
19-
frfr_prgrs_stcd_str: '진화중',
20-
frfr_step_issu_cd: '2단계',
21-
frfr_sttmn_addr: '강원도 속초시',
22-
frfr_frng_dtm: '2025-01-02 06:00:00',
23-
frfr_lctn_xcrd: '128.6',
24-
frfr_lctn_ycrd: '38.2',
18+
frfrInfoId: 'fire-1',
19+
frfrPrgrsStcdNm: '진화중',
20+
frfrStepIssuNm: '2단계',
21+
frfrSttmnAddr: '강원도 속초시',
22+
frfrSttmnAddrDe: '강원도 속초시 대포동 산1',
23+
frfrFrngDtm: '2025-01-02 06:00:00',
24+
frfrLctnXcrd: '128.6',
25+
frfrLctnYcrd: '38.2',
2526
},
2627
],
2728
};
@@ -41,8 +42,10 @@ describe('ForestFireInfoSource', () => {
4142

4243
expect(result.events).toHaveLength(1);
4344
expect(result.events[0].title).toContain('산불');
45+
expect(result.events[0].title).toContain('강원도 속초시');
4446
expect(result.events[0].level).toBe(EventLevels.Severe);
4547
expect(result.events[0].occurredAt).toBe('2025-01-01T21:00:00.000Z');
48+
expect(result.events[0].regionText).toBe('강원도 속초시 대포동 산1');
4649
expect(result.events[0].geo).toEqual({ lat: 38.2, lng: 128.6 });
4750
});
4851

@@ -51,15 +54,16 @@ describe('ForestFireInfoSource', () => {
5154
vi.setSystemTime(new Date('2025-01-02T00:00:00.000Z'));
5255

5356
const responseBody = {
54-
frfrInfoList: [
57+
fireShowInfoList: [
5558
{
56-
frfr_info_id: 'fire-1',
57-
frfr_prgrs_stcd_str: '진화중',
58-
frfr_step_issu_cd: '2단계',
59-
frfr_sttmn_addr: '강원도 속초시',
60-
frfr_frng_dtm: '2025-01-01 06:00:00',
61-
frfr_lctn_xcrd: '128.6',
62-
frfr_lctn_ycrd: '38.2',
59+
frfrInfoId: 'fire-1',
60+
frfrPrgrsStcdNm: '진화중',
61+
frfrStepIssuNm: '2단계',
62+
frfrSttmnAddr: '강원도 속초시',
63+
frfrSttmnAddrDe: '강원도 속초시 대포동 산1',
64+
frfrFrngDtm: '2025-01-01 06:00:00',
65+
frfrLctnXcrd: '128.6',
66+
frfrLctnYcrd: '38.2',
6367
},
6468
],
6569
};
@@ -87,4 +91,188 @@ describe('ForestFireInfoSource', () => {
8791
expect(result.events).toHaveLength(1);
8892
expect(result.events[0].occurredAt).toBe('2025-01-02T00:00:00.000Z');
8993
});
94+
95+
it('should prefer detailed address and map initial step name to minor', async () => {
96+
vi.useFakeTimers();
97+
vi.setSystemTime(new Date('2026-02-19T06:00:00.000Z'));
98+
99+
const responseBody = {
100+
fireShowInfoList: [
101+
{
102+
frfrInfoId: 'fire-2',
103+
frfrPrgrsStcdNm: '진화중',
104+
frfrStepIssuNm: '초기 대응',
105+
frfrSttmnAddr: '대전광역시 대덕구 삼정동',
106+
frfrSttmnAddrDe: '대전광역시 대덕구 삼정동 산17-1임',
107+
frfrFrngDtm: '2026-02-19 11:10:49',
108+
},
109+
],
110+
};
111+
112+
const fetchMock = vi.fn().mockImplementation(() =>
113+
Promise.resolve(
114+
new Response(JSON.stringify(responseBody), {
115+
status: 200,
116+
headers: { 'Content-Type': 'application/json' },
117+
}),
118+
),
119+
);
120+
vi.stubGlobal('fetch', fetchMock);
121+
122+
const source = new ForestFireInfoSource();
123+
const result = await source.run(null);
124+
125+
expect(result.events).toHaveLength(1);
126+
expect(result.events[0].level).toBe(EventLevels.Minor);
127+
expect(result.events[0].regionText).toBe('대전광역시 대덕구 삼정동 산17-1임');
128+
expect(result.events[0].body).toContain('주소: 대전광역시 대덕구 삼정동 산17-1임');
129+
expect(result.events[0].body).toContain('대응 단계: 초기 대응');
130+
});
131+
132+
it('should fallback occurredAt and geo to statement fields', async () => {
133+
vi.useFakeTimers();
134+
vi.setSystemTime(new Date('2026-02-19T06:00:00.000Z'));
135+
136+
const responseBody = {
137+
fireShowInfoList: [
138+
{
139+
frfrInfoId: 'fire-3',
140+
frfrPrgrsStcdNm: '진화중',
141+
frfrStepIssuNm: '2단계',
142+
frfrSttmnAddrDe: '대전광역시 유성구 대정동 산55-35 임',
143+
frfrSttmnDt: '20260219',
144+
frfrSttmnHms: '133252',
145+
frfrSttmnLctnXcrd: '127.30520218650074',
146+
frfrSttmnLctnYcrd: '36.31466400509357',
147+
},
148+
],
149+
};
150+
151+
const fetchMock = vi.fn().mockImplementation(() =>
152+
Promise.resolve(
153+
new Response(JSON.stringify(responseBody), {
154+
status: 200,
155+
headers: { 'Content-Type': 'application/json' },
156+
}),
157+
),
158+
);
159+
vi.stubGlobal('fetch', fetchMock);
160+
161+
const source = new ForestFireInfoSource();
162+
const result = await source.run(null);
163+
164+
expect(result.events).toHaveLength(1);
165+
expect(result.events[0].occurredAt).toBe('2026-02-19T04:32:52.000Z');
166+
expect(result.events[0].geo).toEqual({
167+
lat: 36.31466400509357,
168+
lng: 127.30520218650074,
169+
});
170+
});
171+
172+
it('should use completed time as occurredAt when progress is completed', async () => {
173+
vi.useFakeTimers();
174+
vi.setSystemTime(new Date('2026-02-19T06:00:00.000Z'));
175+
176+
const responseBody = {
177+
fireShowInfoList: [
178+
{
179+
frfrInfoId: 'fire-4',
180+
frfrPrgrsStcdNm: '진화완료',
181+
frfrStepIssuNm: '초기 대응',
182+
frfrSttmnAddrDe: '대전광역시 대덕구 삼정동 산17-1임',
183+
frfrFrngDtm: '2026-02-19 11:10:49',
184+
potfrCmpleDtm: '2026-02-19 11:43:00',
185+
},
186+
],
187+
};
188+
189+
const fetchMock = vi.fn().mockImplementation(() =>
190+
Promise.resolve(
191+
new Response(JSON.stringify(responseBody), {
192+
status: 200,
193+
headers: { 'Content-Type': 'application/json' },
194+
}),
195+
),
196+
);
197+
vi.stubGlobal('fetch', fetchMock);
198+
199+
const source = new ForestFireInfoSource();
200+
const result = await source.run(null);
201+
202+
expect(result.events).toHaveLength(1);
203+
expect(result.events[0].occurredAt).toBe('2026-02-19T02:43:00.000Z');
204+
expect(result.events[0].level).toBe(EventLevels.Info);
205+
});
206+
207+
it('should map progress and step labels from codes when names are missing', async () => {
208+
vi.useFakeTimers();
209+
vi.setSystemTime(new Date('2026-02-19T06:00:00.000Z'));
210+
211+
const responseBody = {
212+
fireShowInfoList: [
213+
{
214+
frfrInfoId: 'fire-5',
215+
frfrPrgrsStcd: '02',
216+
frfrStepIssuCd: '02',
217+
frfrSttmnAddrDe: '충청남도 아산시 영인면 신현리 168-4임',
218+
frfrFrngDtm: '2026-02-19 13:18:04',
219+
},
220+
],
221+
};
222+
223+
const fetchMock = vi.fn().mockImplementation(() =>
224+
Promise.resolve(
225+
new Response(JSON.stringify(responseBody), {
226+
status: 200,
227+
headers: { 'Content-Type': 'application/json' },
228+
}),
229+
),
230+
);
231+
vi.stubGlobal('fetch', fetchMock);
232+
233+
const source = new ForestFireInfoSource();
234+
const result = await source.run(null);
235+
236+
expect(result.events).toHaveLength(1);
237+
expect(result.events[0].level).toBe(EventLevels.Severe);
238+
expect(result.events[0].title).toContain('진화중');
239+
expect(result.events[0].body).toContain('진행 상태: 진화중');
240+
expect(result.events[0].body).toContain('대응 단계: 산불 2단계');
241+
});
242+
243+
it('should use completed progress code to pick completed time', async () => {
244+
vi.useFakeTimers();
245+
vi.setSystemTime(new Date('2026-02-19T06:00:00.000Z'));
246+
247+
const responseBody = {
248+
fireShowInfoList: [
249+
{
250+
frfrInfoId: 'fire-6',
251+
frfrPrgrsStcd: '03',
252+
frfrStepIssuCd: '01',
253+
frfrSttmnAddrDe: '대전광역시 대덕구 삼정동 산17-1임',
254+
frfrFrngDtm: '2026-02-19 11:10:49',
255+
potfrCmpleDtm: '2026-02-19 11:43:00',
256+
},
257+
],
258+
};
259+
260+
const fetchMock = vi.fn().mockImplementation(() =>
261+
Promise.resolve(
262+
new Response(JSON.stringify(responseBody), {
263+
status: 200,
264+
headers: { 'Content-Type': 'application/json' },
265+
}),
266+
),
267+
);
268+
vi.stubGlobal('fetch', fetchMock);
269+
270+
const source = new ForestFireInfoSource();
271+
const result = await source.run(null);
272+
273+
expect(result.events).toHaveLength(1);
274+
expect(result.events[0].occurredAt).toBe('2026-02-19T02:43:00.000Z');
275+
expect(result.events[0].level).toBe(EventLevels.Info);
276+
expect(result.events[0].body).toContain('진행 상태: 진화완료');
277+
});
90278
});

0 commit comments

Comments
 (0)