1+ import { projectMigratableSubscription } from './result-codec.js'
12import { migrationListJsonOutputSchema } from './types.js'
23import { outputMigrationList , serializeMigrationListCsv , serializeMigrationListJson } from './list-output.js'
34import { outputResult } from '@shopify/cli-kit/node/output'
@@ -22,7 +23,7 @@ const CSV_HEADER =
2223 'shop_id,status,manual_subscription_name,manual_subscription_price_amount,manual_subscription_price_currency_code,manual_subscription_interval,target_plan_handle,notification_kind,notification_opt_out_deadline,notification_sent_at,price_behavior,effective_date,last_failure_reason'
2324
2425const CSV_ROW =
25- 'gid://shopify/Shop/1,SCHEDULED,Legacy plan,19.99,USD,EVERY_30_DAYS,standard,NONE,2026-04-01T00:00:00Z,2026-03-01T00:00:00Z,HONOR_BILLING_PRICE,2026-05-01T00:00:00Z ,SCHEDULING_FAILED'
26+ 'gid://shopify/Shop/1,SCHEDULED,Legacy plan,19.99,USD,EVERY_30_DAYS,standard,NONE,2026-04-01T00:00:00Z,2026-03-01T00:00:00Z,HONOR_BILLING_PRICE,2026-05-01 ,SCHEDULING_FAILED'
2627
2728function subscription ( overrides : Partial < MigratableSubscription > = { } ) : MigratableSubscription {
2829 return {
@@ -38,7 +39,7 @@ function subscription(overrides: Partial<MigratableSubscription> = {}): Migratab
3839 sentAt : '2026-03-01T00:00:00Z' ,
3940 } ,
4041 priceBehavior : 'HONOR_BILLING_PRICE' ,
41- effectiveDate : '2026-05-01T00:00:00Z ' ,
42+ effectiveDate : '2026-05-01 ' ,
4243 lastFailureReason : 'SCHEDULING_FAILED' ,
4344 ...overrides ,
4445 }
@@ -84,7 +85,7 @@ describe('migration list serialization', () => {
8485 const expected = `{
8586 "subscriptions": [
8687 {
87- "shopId ": "gid://shopify/Shop/1",
88+ "shopGid ": "gid://shopify/Shop/1",
8889 "status": "SCHEDULED",
8990 "manualSubscriptionName": "Legacy plan",
9091 "manualSubscriptionPrice": {
@@ -99,7 +100,7 @@ describe('migration list serialization', () => {
99100 "sentAt": "2026-03-01T00:00:00Z"
100101 },
101102 "priceBehavior": "HONOR_BILLING_PRICE",
102- "effectiveDate": "2026-05-01T00:00:00Z ",
103+ "effectiveDate": "2026-05-01 ",
103104 "lastFailureReason": "SCHEDULING_FAILED"
104105 }
105106 ]
@@ -109,6 +110,27 @@ describe('migration list serialization', () => {
109110 expect ( serializeMigrationListJson ( subscriptions ) ) . not . toMatch ( / \n $ / )
110111 } )
111112
113+ test ( 'normalizes fractional instants and offsets while preserving calendar dates' , ( ) => {
114+ const value = subscription ( {
115+ notification : { kind : 'NONE' , sentAt : '2026-03-01T01:00:00.999+01:00' , optOutDeadline : null } ,
116+ effectiveDate : '2026-05-01' ,
117+ } )
118+ expect ( JSON . parse ( serializeMigrationListJson ( [ value ] ) ) . subscriptions [ 0 ] ) . toMatchObject ( {
119+ effectiveDate : '2026-05-01' ,
120+ notification : { sentAt : '2026-03-01T00:00:00Z' , optOutDeadline : null } ,
121+ } )
122+ expect ( ( ) =>
123+ migrationListJsonOutputSchema . validate ( {
124+ subscriptions : [
125+ {
126+ ...projectMigratableSubscription ( value ) ,
127+ notification : { kind : 'NONE' , sentAt : '2026-03-01T00:00:00.999Z' , optOutDeadline : null } ,
128+ } ,
129+ ] ,
130+ } ) ,
131+ ) . toThrow ( )
132+ } )
133+
112134 test ( 'serializes CSV fields in the fixed header order without a trailing newline' , ( ) => {
113135 expect ( serializeMigrationListCsv ( [ subscription ( ) ] ) ) . toBe ( `${ CSV_HEADER } \n${ CSV_ROW } ` )
114136 } )
@@ -247,7 +269,7 @@ describe('outputMigrationList JSON', () => {
247269 expect ( outputResult ) . toHaveBeenCalledOnce ( )
248270 const output = vi . mocked ( outputResult ) . mock . calls [ 0 ] ! [ 0 ] as string
249271 expect ( output ) . toBe ( serializeMigrationListJson ( [ ...pageOne , ...pageTwo ] ) )
250- expect ( JSON . parse ( output ) ) . toEqual ( { subscriptions : [ ...pageOne , ...pageTwo ] } )
272+ expect ( JSON . parse ( output ) ) . toEqual ( { subscriptions : [ ...pageOne , ...pageTwo ] . map ( projectMigratableSubscription ) } )
251273 } )
252274
253275 test ( 'writes an empty JSON document for an empty result' , async ( ) => {
@@ -279,7 +301,9 @@ describe('migration list JSON contract', () => {
279301 lastFailureReason : null ,
280302 } )
281303
282- expect ( serializeMigrationListJson ( [ value ] ) ) . toBe ( JSON . stringify ( { subscriptions : [ value ] } , null , 2 ) )
304+ expect ( serializeMigrationListJson ( [ value ] ) ) . toBe (
305+ JSON . stringify ( { subscriptions : [ value ] . map ( projectMigratableSubscription ) } , null , 2 ) ,
306+ )
283307 } )
284308
285309 test . each ( [
@@ -288,7 +312,7 @@ describe('migration list JSON contract', () => {
288312 ] ) ( 'rejects invalid subscription fields: %j' , ( fields ) => {
289313 expect ( ( ) =>
290314 migrationListJsonOutputSchema . validate ( {
291- subscriptions : [ { ...subscription ( ) , ...fields } ] ,
315+ subscriptions : [ { ...projectMigratableSubscription ( subscription ( ) ) , ...fields } ] ,
292316 } ) ,
293317 ) . toThrow ( )
294318 } )
@@ -299,7 +323,7 @@ describe('migration list JSON contract', () => {
299323 { manualSubscriptionInterval : 'MONTHLY' } ,
300324 { lastFailureReason : 'UNKNOWN' } ,
301325 ] ) ( 'accepts unknown server-provided values for pass-through fields: %j' , ( fields ) => {
302- const value = { ...subscription ( ) , ...fields }
326+ const value = { ...projectMigratableSubscription ( subscription ( ) ) , ...fields }
303327
304328 const encoded = migrationListJsonOutputSchema . encode ( { subscriptions : [ value ] } )
305329
0 commit comments