diff --git a/src/services/messages/groupService.ts b/src/services/messages/groupService.ts index c57e5fb..f4cbe5e 100644 --- a/src/services/messages/groupService.ts +++ b/src/services/messages/groupService.ts @@ -176,6 +176,7 @@ export default class GroupService extends DefaultService { /** * 그룹 내 메시지 목록 조회 + * 고속접수 조회 경로는 접수 직후에도 메시지를 반환하며, 일반 그룹도 동일한 형식으로 조회된다. * @param groupId 생성 된 Group ID * @param data startkey, limit 등 쿼리 조건 파라미터 */ @@ -190,7 +191,7 @@ export default class GroupService extends DefaultService { return runSafePromise( this.requestEffect({ httpMethod: 'GET', - url: `messages/v4/groups/${groupId}/messages${parameter}`, + url: `messages/v4/groups/fast/${groupId}/messages${parameter}`, responseSchema: getMessagesResponseSchema, }), ); diff --git a/test/assets/fastGroupMessages.json b/test/assets/fastGroupMessages.json new file mode 100644 index 0000000..df84c10 --- /dev/null +++ b/test/assets/fastGroupMessages.json @@ -0,0 +1,443 @@ +{ + "startKey": null, + "limit": 2, + "messageList": { + "M4V20261105153408TQH6NMEMFZNIWIZ": { + "_id": "M4V20261105153408TQH6NMEMFZNIWIZ", + "accountId": "00000000000000", + "autoTypeDetect": true, + "clusterKey": null, + "country": "82", + "currentQueue": "agent_simulator", + "customFields": {}, + "dateCreated": "2026-11-05T06:34:08.298Z", + "dateProcessed": "2026-11-05T06:34:09.172Z", + "dateReceived": "2026-11-05T06:34:09.000Z", + "dateReported": "2026-11-05T06:34:09.000Z", + "dateUpdated": "2026-11-05T06:34:10.114Z", + "faxOptions": { + "fileIds": [] + }, + "faxPageCount": 0, + "from": "01000000000", + "groupId": "G4V202611051534087P91669DDW55VZ6", + "imageId": null, + "kakaoOptions": { + "senderKey": null, + "templateCode": null, + "templateId": null, + "buttonName": null, + "buttonUrl": null, + "disableSms": false, + "buttons": [], + "pfId": null, + "imageId": null, + "title": null, + "adFlag": false, + "quickReplies": [], + "header": null, + "highlight": { + "title": null, + "description": null, + "imageId": null + }, + "item": { + "list": [], + "summary": { + "title": null, + "description": null + } + }, + "bms": { + "targeting": null, + "chatBubbleType": null, + "adult": false, + "header": null, + "imageId": null, + "imageLink": null, + "additionalContent": null, + "carousel": { + "head": { + "header": null, + "content": null, + "imageId": null, + "imageLink": null, + "linkPc": null, + "linkMobile": null, + "linkAndroid": null, + "linkIos": null + }, + "list": [], + "tail": { + "linkPc": null, + "linkMobile": null, + "linkAndroid": null, + "linkIos": null + } + }, + "mainWideItem": { + "title": null, + "imageId": null, + "linkMobile": null, + "linkPc": null, + "linkAndroid": null, + "linkIos": null + }, + "subWideItemList": [], + "buttons": [], + "coupon": { + "title": null, + "description": null, + "linkMobile": null, + "linkPc": null, + "linkAndroid": null, + "linkIos": null + }, + "commerce": { + "title": null, + "regularPrice": null, + "discountPrice": null, + "discountRate": null, + "discountFixed": null + } + } + }, + "log": [ + { + "createAt": "2026-11-05T06:34:08.298Z", + "message": "메시지가 접수 완료 처리되었습니다." + }, + { + "createAt": "2026-11-05T06:34:08.984Z", + "message": "agent_simulator (으)로 배정되었습니다." + }, + { + "createAt": "2026-11-05T06:34:10.114Z", + "message": "메시지 전송이 실패(3058) 처리 되었습니다. 연동처 결과: 202 - 착신 가입자 없음 전송 실패", + "originalData": { + "MESSAGE_ID": "M4V20261105153408TQH6NMEMFZNIWIZ", + "MSG_ID": 22420, + "SEND_DATE": "20261105153409", + "DELIVER_DATE": "20261105153409", + "REPORT_RES_DATE": "20261105153409", + "RESULT": 0, + "TELCOINFO": "01190", + "TCS_RESULT": 202, + "messageType": "SMS" + } + } + ], + "messageId": "M4V20261105153408TQH6NMEMFZNIWIZ", + "naverOptions": { + "talkId": null, + "templateId": null, + "disableSms": false, + "buttons": [] + }, + "networkCode": "45005", + "originCode": null, + "queues": [ + { + "uniqId": "svc-agent-simulator_22420_M4V20261105153408TQH6NMEMFZNIWIZ", + "name": "agent_simulator", + "statusCode": "3058" + }, + { + "uniqId": null, + "name": "agent_simulator_1", + "statusCode": null + }, + { + "uniqId": null, + "name": "agent_simulator_2", + "statusCode": null + }, + { + "uniqId": null, + "name": "agent_simulator_3", + "statusCode": null + }, + { + "uniqId": null, + "name": "agent_simulator_4", + "statusCode": null + }, + { + "uniqId": null, + "name": "agent_simulator_5", + "statusCode": null + }, + { + "uniqId": null, + "name": "agent_simulator_6", + "statusCode": null + }, + { + "uniqId": null, + "name": "agent_simulator_7", + "statusCode": null + }, + { + "uniqId": null, + "name": "agent_simulator_8", + "statusCode": null + }, + { + "uniqId": null, + "name": "agent_simulator_9", + "statusCode": null + } + ], + "rcsOptions": { + "brandId": null, + "templateId": null, + "mmsType": null, + "copyAllowed": true, + "commercialType": false, + "additionalBody": [], + "buttons": [], + "disableSms": false + }, + "replacement": false, + "replacements": [], + "resendCount": 0, + "status": "COMPLETE", + "statusCode": "3058", + "subject": null, + "text": "list-compare 3", + "to": "01000000000", + "type": "SMS", + "unavailableSenderNumber": false, + "voiceDuration": 0, + "voiceOptions": { + "voiceType": null, + "headerMessage": null, + "tailMessage": null, + "replyRange": null, + "counselorNumber": null + }, + "voiceReplied": 0, + "reason": "전송경로 없음", + "networkName": "SKT" + }, + "M4V20261105153408SKSXHHOQBDRJWMN": { + "_id": "M4V20261105153408SKSXHHOQBDRJWMN", + "accountId": "00000000000000", + "autoTypeDetect": true, + "clusterKey": null, + "country": "82", + "currentQueue": "agent_simulator", + "customFields": {}, + "dateCreated": "2026-11-05T06:34:08.298Z", + "dateProcessed": "2026-11-05T06:34:09.395Z", + "dateReceived": "2026-11-05T06:34:09.000Z", + "dateReported": "2026-11-05T06:34:09.000Z", + "dateUpdated": "2026-11-05T06:34:10.115Z", + "faxOptions": { + "fileIds": [] + }, + "faxPageCount": 0, + "from": "01000000000", + "groupId": "G4V202611051534087P91669DDW55VZ6", + "imageId": null, + "kakaoOptions": { + "senderKey": null, + "templateCode": null, + "templateId": null, + "buttonName": null, + "buttonUrl": null, + "disableSms": false, + "buttons": [], + "pfId": null, + "imageId": null, + "title": null, + "adFlag": false, + "quickReplies": [], + "header": null, + "highlight": { + "title": null, + "description": null, + "imageId": null + }, + "item": { + "list": [], + "summary": { + "title": null, + "description": null + } + }, + "bms": { + "targeting": null, + "chatBubbleType": null, + "adult": false, + "header": null, + "imageId": null, + "imageLink": null, + "additionalContent": null, + "carousel": { + "head": { + "header": null, + "content": null, + "imageId": null, + "imageLink": null, + "linkPc": null, + "linkMobile": null, + "linkAndroid": null, + "linkIos": null + }, + "list": [], + "tail": { + "linkPc": null, + "linkMobile": null, + "linkAndroid": null, + "linkIos": null + } + }, + "mainWideItem": { + "title": null, + "imageId": null, + "linkMobile": null, + "linkPc": null, + "linkAndroid": null, + "linkIos": null + }, + "subWideItemList": [], + "buttons": [], + "coupon": { + "title": null, + "description": null, + "linkMobile": null, + "linkPc": null, + "linkAndroid": null, + "linkIos": null + }, + "commerce": { + "title": null, + "regularPrice": null, + "discountPrice": null, + "discountRate": null, + "discountFixed": null + } + } + }, + "log": [ + { + "createAt": "2026-11-05T06:34:08.298Z", + "message": "메시지가 접수 완료 처리되었습니다." + }, + { + "createAt": "2026-11-05T06:34:08.984Z", + "message": "agent_simulator (으)로 배정되었습니다." + }, + { + "createAt": "2026-11-05T06:34:10.115Z", + "message": "메시지 전송이 실패(3058) 처리 되었습니다. 연동처 결과: 202 - 착신 가입자 없음 전송 실패", + "originalData": { + "MESSAGE_ID": "M4V20261105153408SKSXHHOQBDRJWMN", + "MSG_ID": 22421, + "SEND_DATE": "20261105153409", + "DELIVER_DATE": "20261105153409", + "REPORT_RES_DATE": "20261105153409", + "RESULT": 0, + "TELCOINFO": "01190", + "TCS_RESULT": 202, + "messageType": "SMS" + } + } + ], + "messageId": "M4V20261105153408SKSXHHOQBDRJWMN", + "naverOptions": { + "talkId": null, + "templateId": null, + "disableSms": false, + "buttons": [] + }, + "networkCode": "45005", + "originCode": null, + "queues": [ + { + "uniqId": "svc-agent-simulator_22421_M4V20261105153408SKSXHHOQBDRJWMN", + "name": "agent_simulator", + "statusCode": "3058" + }, + { + "uniqId": null, + "name": "agent_simulator_1", + "statusCode": null + }, + { + "uniqId": null, + "name": "agent_simulator_2", + "statusCode": null + }, + { + "uniqId": null, + "name": "agent_simulator_3", + "statusCode": null + }, + { + "uniqId": null, + "name": "agent_simulator_4", + "statusCode": null + }, + { + "uniqId": null, + "name": "agent_simulator_5", + "statusCode": null + }, + { + "uniqId": null, + "name": "agent_simulator_6", + "statusCode": null + }, + { + "uniqId": null, + "name": "agent_simulator_7", + "statusCode": null + }, + { + "uniqId": null, + "name": "agent_simulator_8", + "statusCode": null + }, + { + "uniqId": null, + "name": "agent_simulator_9", + "statusCode": null + } + ], + "rcsOptions": { + "brandId": null, + "templateId": null, + "mmsType": null, + "copyAllowed": true, + "commercialType": false, + "additionalBody": [], + "buttons": [], + "disableSms": false + }, + "replacement": false, + "replacements": [], + "resendCount": 0, + "status": "COMPLETE", + "statusCode": "3058", + "subject": null, + "text": "list-compare 1", + "to": "01000000000", + "type": "SMS", + "unavailableSenderNumber": false, + "voiceDuration": 0, + "voiceOptions": { + "voiceType": null, + "headerMessage": null, + "tailMessage": null, + "replyRange": null, + "counselorNumber": null + }, + "voiceReplied": 0, + "reason": "전송경로 없음", + "networkName": "SKT" + } + }, + "nextKey": "M4V20261105153408HO57I4VRN0WY7TE" +} diff --git a/test/services/messages/groupService.e2e.test.ts b/test/services/messages/groupService.e2e.test.ts index 05b0e8f..0a0dd9f 100644 --- a/test/services/messages/groupService.e2e.test.ts +++ b/test/services/messages/groupService.e2e.test.ts @@ -34,6 +34,14 @@ describe('GroupService E2E', () => { await expect(groupService.getGroup(nonExistentGroupId)).rejects.toThrow(); }); + it('should return an empty list for a non-existent group', async () => { + const result = await groupService.getGroupMessages( + 'G4V00000000000000000000000000000', + ); + expect(result.messageList).toEqual({}); + expect(result.limit).toBe(20); + }); + it('should handle the full lifecycle of a group', async () => { // 1. Create a group const groupId = await groupService.createGroup(); @@ -47,6 +55,11 @@ describe('GroupService E2E', () => { }; await groupService.addMessagesToGroup(groupId, message); + // 고속접수 조회 경로는 접수 직후에도 메시지 목록을 돌려준다 + const immediate = await groupService.getGroupMessages(groupId, {limit: 20}); + expect(Object.keys(immediate.messageList)).toHaveLength(1); + expect(immediate.limit).toBe(20); + // 3. Get the group info (retry to handle eventual consistency) const groupInfo = await retryWithBackoff( () => groupService.getGroup(groupId), diff --git a/test/services/messages/groupService.fast.test.ts b/test/services/messages/groupService.fast.test.ts index 5df7148..c30acfc 100644 --- a/test/services/messages/groupService.fast.test.ts +++ b/test/services/messages/groupService.fast.test.ts @@ -4,8 +4,11 @@ import {afterEach, beforeEach, describe, expect, it, vi} from 'vitest'; import GroupService from '@/services/messages/groupService'; const fixturePath = join(__dirname, '../../assets/fastGroup.json'); +const listFixturePath = join(__dirname, '../../assets/fastGroupMessages.json'); const loadGroup = (): Record => JSON.parse(readFileSync(fixturePath, 'utf-8')); +const loadGroupMessages = (): Record => + JSON.parse(readFileSync(listFixturePath, 'utf-8')); const jsonResponse = (body: unknown) => new Response(JSON.stringify(body), { @@ -63,6 +66,23 @@ describe('GroupService fast intake group decoding', () => { expect(decoded.app.profit.cti).toBe(0); }); + it('getGroupMessages should read the fast list route and decode the page', async () => { + const page = loadGroupMessages(); + fetchMock.mockResolvedValue(jsonResponse(page)); + const groupId = 'G4V202611051534087P91669DDW55VZ6'; + + const result = await service.getGroupMessages(groupId, {limit: 2}); + + expect(fetchMock.mock.calls[0]?.[0]).toBe( + `https://api.solapi.com/messages/v4/groups/fast/${groupId}/messages?limit=2`, + ); + expect(result.limit).toBe(2); + expect(result.nextKey).toBe(page.nextKey); + expect(Object.keys(result.messageList)).toEqual( + Object.keys(page.messageList as object), + ); + }); + it('getGroup should keep cta/cti values when the server returns them', async () => { const group = loadGroup(); const legacy = {