33 getWildCardDomainName ,
44 parseDateTimeOrDuration ,
55 parseDuration ,
6+ toCfnKeys ,
7+ wait ,
68} from '../utils' ;
79
810beforeAll ( ( ) => {
@@ -27,6 +29,46 @@ describe('parseDuration', () => {
2729 it ( 'should auto-fix 1y durations to 365 days' , ( ) => {
2830 expect ( parseDuration ( '1y' ) . toString ( ) ) . toEqual ( 'P365D' ) ;
2931 } ) ;
32+
33+ it ( 'should accept a number and treat it as hours' , ( ) => {
34+ expect ( parseDuration ( 24 ) . toString ( ) ) . toEqual ( 'PT24H' ) ;
35+ expect ( parseDuration ( 1 ) . toString ( ) ) . toEqual ( 'PT1H' ) ;
36+ } ) ;
37+
38+ it ( 'should default to hours when no unit is provided' , ( ) => {
39+ expect ( parseDuration ( '48' ) . toString ( ) ) . toEqual ( 'PT48H' ) ;
40+ } ) ;
41+
42+ it . each ( [
43+ [ '5m' , 'PT5M' ] ,
44+ [ '10h' , 'PT10H' ] ,
45+ [ '7d' , 'P7D' ] ,
46+ [ '2w' , 'P2W' ] ,
47+ [ '3M' , 'P3M' ] ,
48+ // luxon normalizes quarters into months: 1q → P3M
49+ [ '1q' , 'P3M' ] ,
50+ [ '1s' , 'PT1S' ] ,
51+ [ '500ms' , 'PT0.5S' ] ,
52+ ] ) ( 'should parse "%s" as ISO duration %s' , ( input , expected ) => {
53+ expect ( parseDuration ( input ) . toString ( ) ) . toEqual ( expected ) ;
54+ } ) ;
55+
56+ it ( 'should throw on a non-string non-number input' , ( ) => {
57+ // @ts -expect-error — intentionally passing wrong type to exercise guard
58+ expect ( ( ) => parseDuration ( null ) ) . toThrow (
59+ 'Could not parse null as a valid duration' ,
60+ ) ;
61+ // @ts -expect-error — intentionally passing wrong type to exercise guard
62+ expect ( ( ) => parseDuration ( undefined ) ) . toThrow (
63+ 'Could not parse undefined as a valid duration' ,
64+ ) ;
65+ } ) ;
66+
67+ it ( 'should throw a descriptive error message on unparseable strings' , ( ) => {
68+ expect ( ( ) => parseDuration ( 'not-a-duration' ) ) . toThrow (
69+ 'Could not parse not-a-duration as a valid duration' ,
70+ ) ;
71+ } ) ;
3072} ) ;
3173
3274describe ( 'parseDateTimeOrDuration' , ( ) => {
@@ -47,6 +89,18 @@ describe('parseDateTimeOrDuration', () => {
4789 parseDateTimeOrDuration ( 'foo' ) ;
4890 } ) . toThrowErrorMatchingInlineSnapshot ( `"Invalid date or duration"` ) ;
4991 } ) ;
92+
93+ it ( 'should subtract a day-duration from "now" when given a duration string' , ( ) => {
94+ expect ( parseDateTimeOrDuration ( '1d' ) . toISO ( ) ) . toEqual (
95+ '2019-12-31T17:00:00.000+00:00' ,
96+ ) ;
97+ } ) ;
98+
99+ it ( 'should subtract a week-duration from "now" when given a duration string' , ( ) => {
100+ expect ( parseDateTimeOrDuration ( '1w' ) . toISO ( ) ) . toEqual (
101+ '2019-12-25T17:00:00.000+00:00' ,
102+ ) ;
103+ } ) ;
50104} ) ;
51105
52106describe ( 'domain' , ( ) => {
@@ -58,6 +112,16 @@ describe('domain', () => {
58112 'prod.example.com.' ,
59113 ) ;
60114 } ) ;
115+
116+ it ( 'should handle a two-part domain by returning it unchanged with a trailing dot' , ( ) => {
117+ expect ( getHostedZoneName ( 'example.org' ) ) . toEqual ( 'example.org.' ) ;
118+ } ) ;
119+
120+ it ( 'should strip only the leftmost subdomain when there are many' , ( ) => {
121+ expect ( getHostedZoneName ( 'a.b.c.d.example.com' ) ) . toEqual (
122+ 'b.c.d.example.com.' ,
123+ ) ;
124+ } ) ;
61125 } ) ;
62126
63127 describe ( 'getWildCardDomainName' , ( ) => {
@@ -67,5 +131,73 @@ describe('domain', () => {
67131 '*.prod.example.com' ,
68132 ) ;
69133 } ) ;
134+
135+ it ( 'should replace the leftmost subdomain with a wildcard' , ( ) => {
136+ expect ( getWildCardDomainName ( 'foo.bar.example.com' ) ) . toEqual (
137+ '*.bar.example.com' ,
138+ ) ;
139+ } ) ;
140+ } ) ;
141+ } ) ;
142+
143+ describe ( 'toCfnKeys' , ( ) => {
144+ it ( 'should upper-case the first letter of top-level keys' , ( ) => {
145+ expect ( toCfnKeys ( { foo : 'bar' , baz : 1 } ) ) . toEqual ( { Foo : 'bar' , Baz : 1 } ) ;
146+ } ) ;
147+
148+ it ( 'should recurse into nested objects' , ( ) => {
149+ expect ( toCfnKeys ( { foo : { bar : { baz : 1 } } } ) ) . toEqual ( {
150+ Foo : { Bar : { Baz : 1 } } ,
151+ } ) ;
152+ } ) ;
153+
154+ it ( 'should leave already-capitalized keys alone' , ( ) => {
155+ expect ( toCfnKeys ( { Foo : 1 , BAR : 2 } ) ) . toEqual ( { Foo : 1 , BAR : 2 } ) ;
156+ } ) ;
157+
158+ it ( 'should not modify scalar values' , ( ) => {
159+ expect (
160+ toCfnKeys ( {
161+ a : 'string' ,
162+ b : 42 ,
163+ c : true ,
164+ } ) ,
165+ ) . toEqual ( {
166+ A : 'string' ,
167+ B : 42 ,
168+ C : true ,
169+ } ) ;
170+ } ) ;
171+
172+ it ( 'treats null as an object due to typeof check (pins down current behavior)' , ( ) => {
173+ // typeof null === 'object', so isRecord(null) returns true and lodash's
174+ // transform iterates null as an empty object. This is a known quirk —
175+ // documenting here so any future refactor of isRecord is intentional.
176+ expect ( toCfnKeys ( { value : null } ) ) . toEqual ( { Value : { } } ) ;
177+ } ) ;
178+
179+ it ( 'should handle empty objects' , ( ) => {
180+ expect ( toCfnKeys ( { } ) ) . toEqual ( { } ) ;
181+ } ) ;
182+
183+ it ( 'should not modify arrays (treats them as non-records)' , ( ) => {
184+ // Arrays in JS are typeof 'object', and the helper currently recurses into them;
185+ // this test pins down current behavior so future refactors stay intentional.
186+ const result = toCfnKeys ( { items : [ 1 , 2 , 3 ] } ) ;
187+ expect ( result ) . toHaveProperty ( 'Items' ) ;
188+ } ) ;
189+ } ) ;
190+
191+ describe ( 'wait' , ( ) => {
192+ it ( 'should resolve after the specified time has elapsed' , async ( ) => {
193+ const promise = wait ( 1000 ) ;
194+ jest . advanceTimersByTime ( 1000 ) ;
195+ await expect ( promise ) . resolves . toBeUndefined ( ) ;
196+ } ) ;
197+
198+ it ( 'should resolve immediately when given 0' , async ( ) => {
199+ const promise = wait ( 0 ) ;
200+ jest . advanceTimersByTime ( 0 ) ;
201+ await expect ( promise ) . resolves . toBeUndefined ( ) ;
70202 } ) ;
71203} ) ;
0 commit comments