@@ -471,6 +471,235 @@ describe("evaluate feature targeting integration ", () => {
471471 expect ( res . value ?? false ) . toEqual ( expected ) ;
472472 } ,
473473 ) ;
474+
475+ describe ( "DATE_AFTER and DATE_BEFORE in feature rules" , ( ) => {
476+ it ( "should evaluate DATE_AFTER operator in feature rules" , ( ) => {
477+ const res = evaluateFeatureRules ( {
478+ featureKey : "time_based_feature" ,
479+ rules : [
480+ {
481+ value : "enabled" ,
482+ filter : {
483+ type : "context" ,
484+ field : "user.createdAt" ,
485+ operator : "DATE_AFTER" ,
486+ values : [ "2024-01-01" ] ,
487+ } ,
488+ } ,
489+ ] ,
490+ context : {
491+ user : {
492+ createdAt : "2024-06-15" ,
493+ } ,
494+ } ,
495+ } ) ;
496+
497+ expect ( res ) . toEqual ( {
498+ featureKey : "time_based_feature" ,
499+ value : "enabled" ,
500+ context : {
501+ "user.createdAt" : "2024-06-15" ,
502+ } ,
503+ ruleEvaluationResults : [ true ] ,
504+ reason : "rule #0 matched" ,
505+ missingContextFields : [ ] ,
506+ } ) ;
507+ } ) ;
508+
509+ it ( "should evaluate DATE_BEFORE operator in feature rules" , ( ) => {
510+ const res = evaluateFeatureRules ( {
511+ featureKey : "legacy_feature" ,
512+ rules : [
513+ {
514+ value : "enabled" ,
515+ filter : {
516+ type : "context" ,
517+ field : "user.lastLogin" ,
518+ operator : "DATE_BEFORE" ,
519+ values : [ "2024-12-31" ] ,
520+ } ,
521+ } ,
522+ ] ,
523+ context : {
524+ user : {
525+ lastLogin : "2024-01-15" ,
526+ } ,
527+ } ,
528+ } ) ;
529+
530+ expect ( res ) . toEqual ( {
531+ featureKey : "legacy_feature" ,
532+ value : "enabled" ,
533+ context : {
534+ "user.lastLogin" : "2024-01-15" ,
535+ } ,
536+ ruleEvaluationResults : [ true ] ,
537+ reason : "rule #0 matched" ,
538+ missingContextFields : [ ] ,
539+ } ) ;
540+ } ) ;
541+
542+ it ( "should handle complex rules with DATE_AFTER and DATE_BEFORE in groups" , ( ) => {
543+ const res = evaluateFeatureRules ( {
544+ featureKey : "time_window_feature" ,
545+ rules : [
546+ {
547+ value : "active" ,
548+ filter : {
549+ type : "group" ,
550+ operator : "and" ,
551+ filters : [
552+ {
553+ type : "context" ,
554+ field : "event.startDate" ,
555+ operator : "DATE_AFTER" ,
556+ values : [ "2024-01-01" ] ,
557+ } ,
558+ {
559+ type : "context" ,
560+ field : "event.endDate" ,
561+ operator : "DATE_BEFORE" ,
562+ values : [ "2024-12-31" ] ,
563+ } ,
564+ ] ,
565+ } ,
566+ } ,
567+ ] ,
568+ context : {
569+ event : {
570+ startDate : "2024-06-01" ,
571+ endDate : "2024-11-30" ,
572+ } ,
573+ } ,
574+ } ) ;
575+
576+ expect ( res ) . toEqual ( {
577+ featureKey : "time_window_feature" ,
578+ value : "active" ,
579+ context : {
580+ "event.startDate" : "2024-06-01" ,
581+ "event.endDate" : "2024-11-30" ,
582+ } ,
583+ ruleEvaluationResults : [ true ] ,
584+ reason : "rule #0 matched" ,
585+ missingContextFields : [ ] ,
586+ } ) ;
587+ } ) ;
588+
589+ it ( "should fail when DATE_AFTER condition is not met" , ( ) => {
590+ const res = evaluateFeatureRules ( {
591+ featureKey : "future_feature" ,
592+ rules : [
593+ {
594+ value : "enabled" ,
595+ filter : {
596+ type : "context" ,
597+ field : "user.signupDate" ,
598+ operator : "DATE_AFTER" ,
599+ values : [ "2024-12-01" ] ,
600+ } ,
601+ } ,
602+ ] ,
603+ context : {
604+ user : {
605+ signupDate : "2024-01-15" , // Too early
606+ } ,
607+ } ,
608+ } ) ;
609+
610+ expect ( res ) . toEqual ( {
611+ featureKey : "future_feature" ,
612+ value : undefined ,
613+ context : {
614+ "user.signupDate" : "2024-01-15" ,
615+ } ,
616+ ruleEvaluationResults : [ false ] ,
617+ reason : "no matched rules" ,
618+ missingContextFields : [ ] ,
619+ } ) ;
620+ } ) ;
621+
622+ it ( "should fail when DATE_BEFORE condition is not met" , ( ) => {
623+ const res = evaluateFeatureRules ( {
624+ featureKey : "past_feature" ,
625+ rules : [
626+ {
627+ value : "enabled" ,
628+ filter : {
629+ type : "context" ,
630+ field : "user.lastActivity" ,
631+ operator : "DATE_BEFORE" ,
632+ values : [ "2024-01-01" ] ,
633+ } ,
634+ } ,
635+ ] ,
636+ context : {
637+ user : {
638+ lastActivity : "2024-06-15" , // Too late
639+ } ,
640+ } ,
641+ } ) ;
642+
643+ expect ( res ) . toEqual ( {
644+ featureKey : "past_feature" ,
645+ value : undefined ,
646+ context : {
647+ "user.lastActivity" : "2024-06-15" ,
648+ } ,
649+ ruleEvaluationResults : [ false ] ,
650+ reason : "no matched rules" ,
651+ missingContextFields : [ ] ,
652+ } ) ;
653+ } ) ;
654+
655+ it ( "should work with optimized evaluator" , ( ) => {
656+ const evaluator = newEvaluator ( [
657+ {
658+ value : "time_sensitive" ,
659+ filter : {
660+ type : "group" ,
661+ operator : "and" ,
662+ filters : [
663+ {
664+ type : "context" ,
665+ field : "user.subscriptionDate" ,
666+ operator : "DATE_AFTER" ,
667+ values : [ "2024-01-01" ] ,
668+ } ,
669+ {
670+ type : "context" ,
671+ field : "user.trialEndDate" ,
672+ operator : "DATE_BEFORE" ,
673+ values : [ "2024-12-31" ] ,
674+ } ,
675+ ] ,
676+ } ,
677+ } ,
678+ ] ) ;
679+
680+ const res = evaluator (
681+ {
682+ user : {
683+ subscriptionDate : "2024-03-15" ,
684+ trialEndDate : "2024-09-30" ,
685+ } ,
686+ } ,
687+ "subscription_feature" ,
688+ ) ;
689+
690+ expect ( res ) . toEqual ( {
691+ featureKey : "subscription_feature" ,
692+ value : "time_sensitive" ,
693+ context : {
694+ "user.subscriptionDate" : "2024-03-15" ,
695+ "user.trialEndDate" : "2024-09-30" ,
696+ } ,
697+ ruleEvaluationResults : [ true ] ,
698+ reason : "rule #0 matched" ,
699+ missingContextFields : [ ] ,
700+ } ) ;
701+ } ) ;
702+ } ) ;
474703} ) ;
475704
476705describe ( "operator evaluation" , ( ) => {
@@ -533,6 +762,65 @@ describe("operator evaluation", () => {
533762 expect ( res ) . toEqual ( expected ) ;
534763 } ) ;
535764 }
765+
766+ describe ( "DATE_AFTER and DATE_BEFORE operators" , ( ) => {
767+ const dateTests = [
768+ // DATE_AFTER tests
769+ [ "2024-01-15" , "DATE_AFTER" , "2024-01-10" , true ] , // After
770+ [ "2024-01-10" , "DATE_AFTER" , "2024-01-10" , true ] , // Same date (>=)
771+ [ "2024-01-05" , "DATE_AFTER" , "2024-01-10" , false ] , // Before
772+ [ "2024-12-31" , "DATE_AFTER" , "2024-01-01" , true ] , // Much later
773+ [ "2023-01-01" , "DATE_AFTER" , "2024-01-01" , false ] , // Much earlier
774+
775+ // DATE_BEFORE tests
776+ [ "2024-01-05" , "DATE_BEFORE" , "2024-01-10" , true ] , // Before
777+ [ "2024-01-10" , "DATE_BEFORE" , "2024-01-10" , true ] , // Same date (<=)
778+ [ "2024-01-15" , "DATE_BEFORE" , "2024-01-10" , false ] , // After
779+ [ "2023-01-01" , "DATE_BEFORE" , "2024-01-01" , true ] , // Much earlier
780+ [ "2024-12-31" , "DATE_BEFORE" , "2024-01-01" , false ] , // Much later
781+
782+ // Edge cases with different date formats
783+ [ "2024-01-10T10:30:00Z" , "DATE_AFTER" , "2024-01-10T10:00:00Z" , true ] , // ISO format with time
784+ [ "2024-01-10T09:30:00Z" , "DATE_BEFORE" , "2024-01-10T10:00:00Z" , true ] , // ISO format with time
785+ [
786+ "2024-01-10T10:30:00.123Z" ,
787+ "DATE_AFTER" ,
788+ "2024-01-10T10:00:00.000Z" ,
789+ true ,
790+ ] , // ISO format with time and milliseconds
791+ [
792+ "2024-01-10T09:30:00.123Z" ,
793+ "DATE_BEFORE" ,
794+ "2024-01-10T10:00:00.000Z" ,
795+ true ,
796+ ] , // ISO format with time and milliseconds
797+ [ "01/15/2024" , "DATE_AFTER" , "01/10/2024" , true ] , // US format
798+ [ "01/05/2024" , "DATE_BEFORE" , "01/10/2024" , true ] , // US format
799+ ] as const ;
800+
801+ for ( const [ fieldValue , operator , filterValue , expected ] of dateTests ) {
802+ it ( `evaluates '${ fieldValue } ' ${ operator } '${ filterValue } ' = ${ expected } ` , ( ) => {
803+ const res = evaluate ( fieldValue , operator , [ filterValue ] ) ;
804+ expect ( res ) . toEqual ( expected ) ;
805+ } ) ;
806+ }
807+
808+ it ( "handles invalid date formats gracefully" , ( ) => {
809+ // Invalid dates should result in NaN comparisons and return false
810+ expect ( evaluate ( "invalid-date" , "DATE_AFTER" , [ "2024-01-10" ] ) ) . toBe (
811+ false ,
812+ ) ;
813+ expect ( evaluate ( "2024-01-10" , "DATE_AFTER" , [ "invalid-date" ] ) ) . toBe (
814+ false ,
815+ ) ;
816+ expect ( evaluate ( "invalid-date" , "DATE_BEFORE" , [ "2024-01-10" ] ) ) . toBe (
817+ false ,
818+ ) ;
819+ expect ( evaluate ( "2024-01-10" , "DATE_BEFORE" , [ "invalid-date" ] ) ) . toBe (
820+ false ,
821+ ) ;
822+ } ) ;
823+ } ) ;
536824} ) ;
537825
538826describe ( "rollout hash" , ( ) => {
0 commit comments