@@ -9,29 +9,74 @@ describe("SimulationService", () => {
99 } ) ;
1010
1111 describe ( "calculateNextRenewal" , ( ) => {
12+ it ( "should add 1 week for weekly billing cycle" , ( ) => {
13+ const currentDate = new Date ( "2024-01-01T00:00:00.000Z" ) ;
14+ const nextDate = service . calculateNextRenewal ( currentDate , "weekly" ) ;
15+ expect ( nextDate . toISOString ( ) ) . toBe ( new Date ( "2024-01-08T00:00:00.000Z" ) . toISOString ( ) ) ;
16+ } ) ;
17+
1218 it ( "should add 1 month for monthly billing cycle" , ( ) => {
1319 const currentDate = new Date ( "2024-01-01T00:00:00.000Z" ) ;
1420 const nextDate = service . calculateNextRenewal ( currentDate , "monthly" ) ;
15-
1621 expect ( nextDate . toISOString ( ) ) . toBe ( new Date ( "2024-02-01T00:00:00.000Z" ) . toISOString ( ) ) ;
1722 } ) ;
1823
1924 it ( "should add 1 quarter for quarterly billing cycle" , ( ) => {
2025 const currentDate = new Date ( "2024-01-01T00:00:00.000Z" ) ;
2126 const nextDate = service . calculateNextRenewal ( currentDate , "quarterly" ) ;
22-
2327 expect ( nextDate . toISOString ( ) ) . toBe ( new Date ( "2024-04-01T00:00:00.000Z" ) . toISOString ( ) ) ;
2428 } ) ;
2529
26-
27- it ( "should add 365 days for yearly billing cycle" , ( ) => {
28- const currentDate = new Date ( "2024-01-01" ) ;
30+ it ( "should add 1 year for yearly billing cycle" , ( ) => {
31+ const currentDate = new Date ( "2024-01-01T00:00:00.000Z" ) ;
2932 const nextDate = service . calculateNextRenewal ( currentDate , "yearly" ) ;
33+ expect ( nextDate . toISOString ( ) ) . toBe ( new Date ( "2025-01-01T00:00:00.000Z" ) . toISOString ( ) ) ;
34+ } ) ;
3035
31- expect ( nextDate . toISOString ( ) ) . toBe (
32- new Date ( "2025-01-01" ) . toISOString ( )
36+ it ( "should treat annual the same as yearly" , ( ) => {
37+ const d = new Date ( "2024-06-15T00:00:00.000Z" ) ;
38+ expect ( service . calculateNextRenewal ( d , "annual" ) . toISOString ( ) ) . toBe (
39+ service . calculateNextRenewal ( d , "yearly" ) . toISOString ( )
3340 ) ;
3441 } ) ;
42+
43+ // Month-end edge cases — the whole point of using date-fns over manual arithmetic
44+ it ( "should clamp Jan 31 + 1 month to Feb 29 on a leap year" , ( ) => {
45+ const jan31 = new Date ( "2024-01-31T00:00:00.000Z" ) ; // 2024 is a leap year
46+ const next = service . calculateNextRenewal ( jan31 , "monthly" ) ;
47+ expect ( next . toISOString ( ) ) . toBe ( new Date ( "2024-02-29T00:00:00.000Z" ) . toISOString ( ) ) ;
48+ } ) ;
49+
50+ it ( "should clamp Jan 31 + 1 month to Feb 28 on a non-leap year" , ( ) => {
51+ const jan31 = new Date ( "2023-01-31T00:00:00.000Z" ) ;
52+ const next = service . calculateNextRenewal ( jan31 , "monthly" ) ;
53+ expect ( next . toISOString ( ) ) . toBe ( new Date ( "2023-02-28T00:00:00.000Z" ) . toISOString ( ) ) ;
54+ } ) ;
55+
56+ it ( "should clamp Mar 31 + 1 quarter to Jun 30" , ( ) => {
57+ const mar31 = new Date ( "2024-03-31T00:00:00.000Z" ) ;
58+ const next = service . calculateNextRenewal ( mar31 , "quarterly" ) ;
59+ expect ( next . toISOString ( ) ) . toBe ( new Date ( "2024-06-30T00:00:00.000Z" ) . toISOString ( ) ) ;
60+ } ) ;
61+
62+ it ( "no drift: applying monthly 12 times from Jan 31 lands on Jan 31 the next year" , ( ) => {
63+ let date = new Date ( "2024-01-31T00:00:00.000Z" ) ;
64+ for ( let i = 0 ; i < 12 ; i ++ ) {
65+ date = service . calculateNextRenewal ( date , "monthly" ) ;
66+ }
67+ expect ( date . getUTCDate ( ) ) . toBe ( 31 ) ;
68+ expect ( date . getUTCMonth ( ) ) . toBe ( 0 ) ; // January
69+ expect ( date . getUTCFullYear ( ) ) . toBe ( 2025 ) ;
70+ } ) ;
71+
72+ it ( "no drift: applying weekly 52 times from a given date returns the same weekday one year later" , ( ) => {
73+ const start = new Date ( "2024-01-01T00:00:00.000Z" ) ; // Monday
74+ let date = start ;
75+ for ( let i = 0 ; i < 52 ; i ++ ) {
76+ date = service . calculateNextRenewal ( date , "weekly" ) ;
77+ }
78+ expect ( date . getUTCDay ( ) ) . toBe ( start . getUTCDay ( ) ) ;
79+ } ) ;
3580 } ) ;
3681
3782 describe ( "projectSubscriptionRenewals" , ( ) => {
0 commit comments