From a0613bc7622a0f8fd3a25bc9e5fa85eede5107e6 Mon Sep 17 00:00:00 2001 From: dino Date: Fri, 6 Nov 2026 07:27:44 +0900 Subject: [PATCH 1/2] fix(statistics): tolerate an empty statistics response The statistics endpoint returns {} for accounts or date ranges that have no daily statistics, which failed the response schema with "balance: [Missing]". Numeric fields now default to 0, monthPeriod to [] and the period totals to zeroed records. Closes #182 --- src/models/responses/messageResponses.ts | 35 +- test/assets/statistics.json | 461 ++++++++++++++++++ .../responses/getStatisticsResponse.test.ts | 56 +++ 3 files changed, 543 insertions(+), 9 deletions(-) create mode 100644 test/assets/statistics.json create mode 100644 test/models/responses/getStatisticsResponse.test.ts diff --git a/src/models/responses/messageResponses.ts b/src/models/responses/messageResponses.ts index 328da7f..124fe3f 100644 --- a/src/models/responses/messageResponses.ts +++ b/src/models/responses/messageResponses.ts @@ -95,7 +95,7 @@ export type GetGroupsResponse = Schema.Schema.Type< typeof getGroupsResponseSchema >; -const statisticsPeriodResultSchema = Schema.Struct({ +const statisticsPeriodResultFields = { total: Schema.Number, sms: Schema.Number, lms: Schema.Number, @@ -121,7 +121,21 @@ const statisticsPeriodResultSchema = Schema.Struct({ bms_commerce: Schema.optional(Schema.Number), bms_carousel_commerce: Schema.optional(Schema.Number), bms_free: Schema.optional(Schema.Number), +}; +const statisticsPeriodResultSchema = Schema.Struct( + statisticsPeriodResultFields, +); +type StatisticsPeriodResult = Schema.Schema.Type< + typeof statisticsPeriodResultSchema +>; +const emptyStatisticsPeriodResult = (): StatisticsPeriodResult => + Object.fromEntries( + Object.keys(statisticsPeriodResultFields).map(key => [key, 0]), + ) as StatisticsPeriodResult; +const optionalPeriodResult = Schema.optionalWith(statisticsPeriodResultSchema, { + default: emptyStatisticsPeriodResult, }); +const optionalZero = Schema.optionalWith(Schema.Number, {default: () => 0}); const refundSchema = Schema.Struct({ balance: Schema.Number, @@ -170,17 +184,20 @@ const monthPeriodSchema = Schema.Struct({ failed: statisticsPeriodResultSchema, }); +// 통계 문서가 없는 계정·기간은 서버가 빈 객체 {} 를 돌려주므로 모든 필드를 기본값으로 채운다 export const getStatisticsResponseSchema = Schema.Struct({ - balance: Schema.Number, - point: Schema.Number, + balance: optionalZero, + point: optionalZero, deposit: Schema.optional(Schema.Number), - monthlyBalanceAvg: Schema.Number, - monthlyPointAvg: Schema.Number, + monthlyBalanceAvg: optionalZero, + monthlyPointAvg: optionalZero, monthlyDepositAvg: Schema.optional(Schema.Number), - monthPeriod: Schema.Array(monthPeriodSchema), - total: statisticsPeriodResultSchema, - successed: statisticsPeriodResultSchema, - failed: statisticsPeriodResultSchema, + monthPeriod: Schema.optionalWith(Schema.Array(monthPeriodSchema), { + default: () => [], + }), + total: optionalPeriodResult, + successed: optionalPeriodResult, + failed: optionalPeriodResult, dailyBalanceAvg: Schema.optional(Schema.Number), dailyPointAvg: Schema.optional(Schema.Number), dailyTotalCountAvg: Schema.optional(Schema.Number), diff --git a/test/assets/statistics.json b/test/assets/statistics.json new file mode 100644 index 0000000..9d5673f --- /dev/null +++ b/test/assets/statistics.json @@ -0,0 +1,461 @@ +{ + "balance": 0, + "point": 0, + "deposit": 0, + "monthlyBalanceAvg": 0, + "monthlyPointAvg": 0, + "monthlyDepositAvg": 0, + "monthPeriod": [ + { + "date": "2026/11", + "balance": 0, + "balanceAvg": 0, + "point": 0, + "pointAvg": 0, + "deposit": 0, + "depositAvg": 0, + "dayPeriod": [ + { + "_id": "2026-11-04T15:00:00.000Z", + "month": "2026/11", + "date": "2026/11/05", + "balance": 0, + "point": 0, + "deposit": 0, + "statusCode": { + "2011": { + "mms": 1242 + }, + "2061": { + "sms": 1242 + }, + "3058": { + "fax": 1242, + "lms": 1248, + "nsa": 1242, + "rcs_itpl": 1242, + "rcs_lms": 1242, + "rcs_ltpl": 1242, + "rcs_mms": 1242, + "rcs_sms": 1242, + "rcs_tpl": 1242, + "sms": 4189, + "voice": 1242 + }, + "3104": { + "ata": 2486, + "bms_carousel_commerce": 1242, + "bms_carousel_feed": 1242, + "bms_commerce": 1242, + "bms_free": 1249, + "bms_image": 1242, + "bms_premium_video": 1242, + "bms_text": 1242, + "bms_wide": 1242, + "bms_wide_item_list": 1242 + } + }, + "refund": { + "balance": 0, + "point": 0, + "deposit": 0 + }, + "total": { + "total": 32770, + "sms": 5431, + "lms": 1248, + "mms": 1242, + "ata": 2486, + "cta": 0, + "cti": 0, + "nsa": 1242, + "rcs_sms": 1242, + "rcs_lms": 1242, + "rcs_mms": 1242, + "rcs_tpl": 1242, + "rcs_itpl": 1242, + "rcs_ltpl": 1242, + "fax": 1242, + "voice": 1242, + "bms_text": 1242, + "bms_image": 1242, + "bms_wide": 1242, + "bms_wide_item_list": 1242, + "bms_carousel_feed": 1242, + "bms_premium_video": 1242, + "bms_commerce": 1242, + "bms_carousel_commerce": 1242, + "bms_free": 1249 + }, + "successed": { + "total": 0, + "sms": 0, + "lms": 0, + "mms": 0, + "ata": 0, + "cta": 0, + "cti": 0, + "nsa": 0, + "rcs_sms": 0, + "rcs_lms": 0, + "rcs_mms": 0, + "rcs_tpl": 0, + "rcs_itpl": 0, + "rcs_ltpl": 0, + "fax": 0, + "voice": 0, + "bms_text": 0, + "bms_image": 0, + "bms_wide": 0, + "bms_wide_item_list": 0, + "bms_carousel_feed": 0, + "bms_premium_video": 0, + "bms_commerce": 0, + "bms_carousel_commerce": 0, + "bms_free": 0 + }, + "failed": { + "total": 32770, + "sms": 5431, + "lms": 1248, + "mms": 1242, + "ata": 2486, + "cta": 0, + "cti": 0, + "nsa": 1242, + "rcs_sms": 1242, + "rcs_lms": 1242, + "rcs_mms": 1242, + "rcs_tpl": 1242, + "rcs_itpl": 1242, + "rcs_ltpl": 1242, + "fax": 1242, + "voice": 1242, + "bms_text": 1242, + "bms_image": 1242, + "bms_wide": 1242, + "bms_wide_item_list": 1242, + "bms_carousel_feed": 1242, + "bms_premium_video": 1242, + "bms_commerce": 1242, + "bms_carousel_commerce": 1242, + "bms_free": 1249 + } + } + ], + "refund": { + "balance": 0, + "balanceAvg": 0, + "point": 0, + "pointAvg": 0, + "deposit": 0, + "depositAvg": 0 + }, + "total": { + "total": 32770, + "sms": 5431, + "lms": 1248, + "mms": 1242, + "ata": 2486, + "cta": 0, + "cti": 0, + "nsa": 1242, + "rcs_sms": 1242, + "rcs_lms": 1242, + "rcs_mms": 1242, + "rcs_tpl": 1242, + "rcs_itpl": 1242, + "rcs_ltpl": 1242, + "fax": 1242, + "voice": 1242, + "bms_text": 1242, + "bms_image": 1242, + "bms_wide": 1242, + "bms_wide_item_list": 1242, + "bms_carousel_feed": 1242, + "bms_premium_video": 1242, + "bms_commerce": 1242, + "bms_carousel_commerce": 1242, + "bms_free": 1249 + }, + "successed": { + "total": 0, + "sms": 0, + "lms": 0, + "mms": 0, + "ata": 0, + "cta": 0, + "cti": 0, + "nsa": 0, + "rcs_sms": 0, + "rcs_lms": 0, + "rcs_mms": 0, + "rcs_tpl": 0, + "rcs_itpl": 0, + "rcs_ltpl": 0, + "fax": 0, + "voice": 0, + "bms_text": 0, + "bms_image": 0, + "bms_wide": 0, + "bms_wide_item_list": 0, + "bms_carousel_feed": 0, + "bms_premium_video": 0, + "bms_commerce": 0, + "bms_carousel_commerce": 0, + "bms_free": 0 + }, + "failed": { + "total": 32770, + "sms": 5431, + "lms": 1248, + "mms": 1242, + "ata": 2486, + "cta": 0, + "cti": 0, + "nsa": 1242, + "rcs_sms": 1242, + "rcs_lms": 1242, + "rcs_mms": 1242, + "rcs_tpl": 1242, + "rcs_itpl": 1242, + "rcs_ltpl": 1242, + "fax": 1242, + "voice": 1242, + "bms_text": 1242, + "bms_image": 1242, + "bms_wide": 1242, + "bms_wide_item_list": 1242, + "bms_carousel_feed": 1242, + "bms_premium_video": 1242, + "bms_commerce": 1242, + "bms_carousel_commerce": 1242, + "bms_free": 1249 + } + } + ], + "refund": { + "balance": 0, + "balanceAvg": 0, + "point": 0, + "pointAvg": 0, + "deposit": 0, + "depositAvg": 0 + }, + "dayPeriod": [ + { + "_id": "2026-11-04T15:00:00.000Z", + "month": "2026/11", + "date": "2026/11/05", + "balance": 0, + "point": 0, + "deposit": 0, + "statusCode": { + "2011": { + "mms": 1242 + }, + "2061": { + "sms": 1242 + }, + "3058": { + "fax": 1242, + "lms": 1248, + "nsa": 1242, + "rcs_itpl": 1242, + "rcs_lms": 1242, + "rcs_ltpl": 1242, + "rcs_mms": 1242, + "rcs_sms": 1242, + "rcs_tpl": 1242, + "sms": 4189, + "voice": 1242 + }, + "3104": { + "ata": 2486, + "bms_carousel_commerce": 1242, + "bms_carousel_feed": 1242, + "bms_commerce": 1242, + "bms_free": 1249, + "bms_image": 1242, + "bms_premium_video": 1242, + "bms_text": 1242, + "bms_wide": 1242, + "bms_wide_item_list": 1242 + } + }, + "refund": { + "balance": 0, + "point": 0, + "deposit": 0 + }, + "total": { + "total": 32770, + "sms": 5431, + "lms": 1248, + "mms": 1242, + "ata": 2486, + "cta": 0, + "cti": 0, + "nsa": 1242, + "rcs_sms": 1242, + "rcs_lms": 1242, + "rcs_mms": 1242, + "rcs_tpl": 1242, + "rcs_itpl": 1242, + "rcs_ltpl": 1242, + "fax": 1242, + "voice": 1242, + "bms_text": 1242, + "bms_image": 1242, + "bms_wide": 1242, + "bms_wide_item_list": 1242, + "bms_carousel_feed": 1242, + "bms_premium_video": 1242, + "bms_commerce": 1242, + "bms_carousel_commerce": 1242, + "bms_free": 1249 + }, + "successed": { + "total": 0, + "sms": 0, + "lms": 0, + "mms": 0, + "ata": 0, + "cta": 0, + "cti": 0, + "nsa": 0, + "rcs_sms": 0, + "rcs_lms": 0, + "rcs_mms": 0, + "rcs_tpl": 0, + "rcs_itpl": 0, + "rcs_ltpl": 0, + "fax": 0, + "voice": 0, + "bms_text": 0, + "bms_image": 0, + "bms_wide": 0, + "bms_wide_item_list": 0, + "bms_carousel_feed": 0, + "bms_premium_video": 0, + "bms_commerce": 0, + "bms_carousel_commerce": 0, + "bms_free": 0 + }, + "failed": { + "total": 32770, + "sms": 5431, + "lms": 1248, + "mms": 1242, + "ata": 2486, + "cta": 0, + "cti": 0, + "nsa": 1242, + "rcs_sms": 1242, + "rcs_lms": 1242, + "rcs_mms": 1242, + "rcs_tpl": 1242, + "rcs_itpl": 1242, + "rcs_ltpl": 1242, + "fax": 1242, + "voice": 1242, + "bms_text": 1242, + "bms_image": 1242, + "bms_wide": 1242, + "bms_wide_item_list": 1242, + "bms_carousel_feed": 1242, + "bms_premium_video": 1242, + "bms_commerce": 1242, + "bms_carousel_commerce": 1242, + "bms_free": 1249 + } + } + ], + "total": { + "total": 32770, + "sms": 5431, + "lms": 1248, + "mms": 1242, + "ata": 2486, + "cta": 0, + "cti": 0, + "nsa": 1242, + "rcs_sms": 1242, + "rcs_lms": 1242, + "rcs_mms": 1242, + "rcs_tpl": 1242, + "rcs_itpl": 1242, + "rcs_ltpl": 1242, + "fax": 1242, + "voice": 1242, + "bms_text": 1242, + "bms_image": 1242, + "bms_wide": 1242, + "bms_wide_item_list": 1242, + "bms_carousel_feed": 1242, + "bms_premium_video": 1242, + "bms_commerce": 1242, + "bms_carousel_commerce": 1242, + "bms_free": 1249 + }, + "successed": { + "total": 0, + "sms": 0, + "lms": 0, + "mms": 0, + "ata": 0, + "cta": 0, + "cti": 0, + "nsa": 0, + "rcs_sms": 0, + "rcs_lms": 0, + "rcs_mms": 0, + "rcs_tpl": 0, + "rcs_itpl": 0, + "rcs_ltpl": 0, + "fax": 0, + "voice": 0, + "bms_text": 0, + "bms_image": 0, + "bms_wide": 0, + "bms_wide_item_list": 0, + "bms_carousel_feed": 0, + "bms_premium_video": 0, + "bms_commerce": 0, + "bms_carousel_commerce": 0, + "bms_free": 0 + }, + "failed": { + "total": 32770, + "sms": 5431, + "lms": 1248, + "mms": 1242, + "ata": 2486, + "cta": 0, + "cti": 0, + "nsa": 1242, + "rcs_sms": 1242, + "rcs_lms": 1242, + "rcs_mms": 1242, + "rcs_tpl": 1242, + "rcs_itpl": 1242, + "rcs_ltpl": 1242, + "fax": 1242, + "voice": 1242, + "bms_text": 1242, + "bms_image": 1242, + "bms_wide": 1242, + "bms_wide_item_list": 1242, + "bms_carousel_feed": 1242, + "bms_premium_video": 1242, + "bms_commerce": 1242, + "bms_carousel_commerce": 1242, + "bms_free": 1249 + }, + "dailyBalanceAvg": 0, + "dailyPointAvg": 0, + "dailyDepositAvg": 0, + "dailyTotalCountAvg": 32770, + "dailyFailedCountAvg": 32770, + "dailySuccessedCountAvg": 0 +} diff --git a/test/models/responses/getStatisticsResponse.test.ts b/test/models/responses/getStatisticsResponse.test.ts new file mode 100644 index 0000000..7a273ac --- /dev/null +++ b/test/models/responses/getStatisticsResponse.test.ts @@ -0,0 +1,56 @@ +import {readFileSync} from 'node:fs'; +import {join} from 'node:path'; +import {Either, Schema} from 'effect'; +import {describe, expect, it} from 'vitest'; +import {getStatisticsResponseSchema} from '@/models/responses/messageResponses'; + +const fixture = JSON.parse( + readFileSync(join(__dirname, '../../assets/statistics.json'), 'utf-8'), +); + +describe('getStatisticsResponseSchema', () => { + it('should decode a full statistics response unchanged', () => { + const result = Schema.decodeUnknownSync(getStatisticsResponseSchema)( + fixture, + ); + + expect(result.balance).toBe(fixture.balance); + expect(result.monthPeriod).toHaveLength(fixture.monthPeriod.length); + expect(result.total.total).toBe(fixture.total.total); + expect(result.failed.sms).toBe(fixture.failed.sms); + }); + + it('should fill defaults when the server returns an empty object (#182)', () => { + const result = Schema.decodeUnknownSync(getStatisticsResponseSchema)({}); + + expect(result.balance).toBe(0); + expect(result.point).toBe(0); + expect(result.monthlyBalanceAvg).toBe(0); + expect(result.monthlyPointAvg).toBe(0); + expect(result.monthPeriod).toEqual([]); + expect(result.total.total).toBe(0); + expect(result.successed.sms).toBe(0); + expect(result.failed.bms_free).toBe(0); + expect(result.deposit).toBeUndefined(); + }); + + it('should keep provided values when only some fields are present', () => { + const result = Schema.decodeUnknownSync(getStatisticsResponseSchema)({ + balance: 1200, + point: 30, + }); + + expect(result.balance).toBe(1200); + expect(result.point).toBe(30); + expect(result.monthPeriod).toEqual([]); + expect(result.total.total).toBe(0); + }); + + it('should still reject wrong field types', () => { + const result = Schema.decodeUnknownEither(getStatisticsResponseSchema)({ + balance: 'many', + }); + + expect(Either.isLeft(result)).toBe(true); + }); +}); From 6469ae95080eecddb31a514c2040e54a74e3d8f4 Mon Sep 17 00:00:00 2001 From: dino Date: Fri, 6 Nov 2026 07:27:44 +0900 Subject: [PATCH 2/2] feat(messages): support criteria/cond/value search in getMessages Exposes the advanced search parameters of /messages/v4/list. The allowed criteria and conditions mirror the server validation, arrays or single strings are accepted, and the three lists must be given together with equal length. Values are joined with commas, so a value containing a comma is rejected up front. Closes #183 --- .../requests/messages/getMessagesRequest.ts | 66 ++++++++++++++++ .../messages/getMessagesRequest.test.ts | 78 ++++++++++++++++++- 2 files changed, 143 insertions(+), 1 deletion(-) diff --git a/src/models/requests/messages/getMessagesRequest.ts b/src/models/requests/messages/getMessagesRequest.ts index 47f53a5..12898ee 100644 --- a/src/models/requests/messages/getMessagesRequest.ts +++ b/src/models/requests/messages/getMessagesRequest.ts @@ -5,6 +5,45 @@ import {messageTypeSchema} from '../../base/messages/message'; export const dateTypeSchema = Schema.Literal('CREATED', 'UPDATED'); export type DateType = Schema.Schema.Type; +export const messageSearchCriteriaSchema = Schema.Literal( + 'messageId', + 'groupId', + 'to', + 'from', + 'type', + 'statusCode', + 'dateCreated', + 'dateUpdated', + 'replacement', + 'coolsmsMessageId', + 'coolsmsGroupId', + 'status', + 'kakaoTemplateId', +); +export type MessageSearchCriteria = Schema.Schema.Type< + typeof messageSearchCriteriaSchema +>; + +export const messageSearchConditionSchema = Schema.Literal( + 'eq', + 'ne', + 'gte', + 'gt', + 'lt', + 'lte', +); +export type MessageSearchCondition = Schema.Schema.Type< + typeof messageSearchConditionSchema +>; + +const oneOrMany = (item: Schema.Schema) => + Schema.Union(item, Schema.Array(item)); + +const toList = ( + input: T | ReadonlyArray | undefined, +): ReadonlyArray => + input == null ? [] : Array.isArray(input) ? input : [input as T]; + const baseGetMessagesRequestSchema = Schema.Struct({ startKey: Schema.optional(Schema.String), limit: Schema.optional(Schema.Number), @@ -18,6 +57,9 @@ const baseGetMessagesRequestSchema = Schema.Struct({ dateType: Schema.optional(dateTypeSchema), startDate: Schema.optional(Schema.Union(Schema.String, Schema.DateFromSelf)), endDate: Schema.optional(Schema.Union(Schema.String, Schema.DateFromSelf)), + criteria: Schema.optional(oneOrMany(messageSearchCriteriaSchema)), + cond: Schema.optional(oneOrMany(messageSearchConditionSchema)), + value: Schema.optional(oneOrMany(Schema.String)), }); export const getMessagesRequestSchema = baseGetMessagesRequestSchema.pipe( @@ -27,6 +69,20 @@ export const getMessagesRequestSchema = baseGetMessagesRequestSchema.pipe( if (hasDateType && !hasDate) { return 'dateType은 startDate 또는 endDate와 함께 사용해야 합니다.'; } + const criteria = toList(data.criteria); + const cond = toList(data.cond); + const value = toList(data.value); + const used = [criteria, cond, value].filter(list => list.length > 0); + if (used.length === 0) return true; + if (used.length < 3) { + return 'criteria, cond, value는 함께 지정해야 합니다.'; + } + if (criteria.length !== cond.length || cond.length !== value.length) { + return 'criteria, cond, value의 개수가 같아야 합니다.'; + } + if (value.some(v => v.includes(','))) { + return 'value에는 쉼표(,)를 포함할 수 없습니다.'; + } return true; }), ); @@ -75,6 +131,9 @@ export type GetMessagesFinalizedPayload = { statusCode?: string; startDate?: string; endDate?: string; + criteria?: string; + cond?: string; + value?: string; }; export function finalizeGetMessagesRequest( @@ -98,6 +157,13 @@ export function finalizeGetMessagesRequest( statusCode: data.statusCode, }; + const criteria = toList(data.criteria); + if (criteria.length > 0) { + payload.criteria = criteria.join(','); + payload.cond = toList(data.cond).join(','); + payload.value = toList(data.value).join(','); + } + if (data.startDate != null) { payload.startDate = formatWithTransfer(data.startDate); } diff --git a/test/models/requests/messages/getMessagesRequest.test.ts b/test/models/requests/messages/getMessagesRequest.test.ts index 5e6dab8..edcb222 100644 --- a/test/models/requests/messages/getMessagesRequest.test.ts +++ b/test/models/requests/messages/getMessagesRequest.test.ts @@ -1,4 +1,4 @@ -import {Schema} from 'effect'; +import {Either, Schema} from 'effect'; import {describe, expect, it} from 'vitest'; import {InvalidDateError} from '@/errors/defaultError'; import { @@ -108,3 +108,79 @@ describe('finalizeGetMessagesRequest', () => { expect(result.startKey).toBe('key123'); }); }); + +describe('getMessagesRequestSchema advanced search (#183)', () => { + const decode = Schema.decodeUnknownEither(getMessagesRequestSchema); + + it('should accept matching criteria, cond and value arrays', () => { + const result = decode({ + criteria: ['type', 'statusCode'], + cond: ['eq', 'eq'], + value: ['SMS', '2000'], + }); + expect(Either.isRight(result)).toBe(true); + }); + + it('should accept a single string for each field', () => { + const result = decode({criteria: 'to', cond: 'eq', value: '01000000000'}); + expect(Either.isRight(result)).toBe(true); + }); + + const rejected: Array<[unknown, string]> = [ + [{criteria: ['type'], cond: ['eq']}, 'value 누락'], + [{criteria: ['type'], cond: ['eq', 'ne'], value: ['SMS']}, '개수 불일치'], + [ + {criteria: ['type'], cond: ['like'], value: ['SMS']}, + '허용되지 않는 cond', + ], + [ + {criteria: ['text'], cond: ['eq'], value: ['hi']}, + '허용되지 않는 criteria', + ], + [{criteria: ['to'], cond: ['eq'], value: ['010,020']}, 'value 에 쉼표'], + ]; + it.each(rejected)('should reject %j (%s)', input => { + expect(Either.isLeft(decode(input))).toBe(true); + }); + + it('should accept an empty criteria set', () => { + expect(Either.isRight(decode({criteria: [], cond: [], value: []}))).toBe( + true, + ); + }); +}); + +describe('finalizeGetMessagesRequest advanced search (#183)', () => { + it('should join criteria, cond and value with commas', () => { + const payload = finalizeGetMessagesRequest({ + limit: 5, + criteria: ['type', 'dateCreated'], + cond: ['eq', 'gte'], + value: ['SMS', '2026-11-05'], + }); + expect(payload.criteria).toBe('type,dateCreated'); + expect(payload.cond).toBe('eq,gte'); + expect(payload.value).toBe('SMS,2026-11-05'); + expect(payload.limit).toBe(5); + }); + + it('should pass single strings through unchanged', () => { + const payload = finalizeGetMessagesRequest({ + criteria: 'groupId', + cond: 'eq', + value: 'G4V0', + }); + expect(payload).toMatchObject({ + criteria: 'groupId', + cond: 'eq', + value: 'G4V0', + }); + }); + + it('should omit search keys when criteria is empty', () => { + const payload = finalizeGetMessagesRequest({limit: 1}); + expect(payload).not.toHaveProperty('criteria'); + expect(payload).not.toHaveProperty('cond'); + expect(payload).not.toHaveProperty('value'); + }); +});