@@ -391,6 +391,194 @@ func customerIDs(customers []customer.Customer) []string {
391391 })
392392}
393393
394+ func TestGetCustomerByUsageAttribution (t * testing.T ) {
395+ t .Run ("MatchByCustomerKey" , func (t * testing.T ) {
396+ env := newTestEnv (t )
397+ ns := ulid .Make ().String ()
398+ id := env .seedCustomerWithKey (ns , "cust-key" , "subj-1" )
399+
400+ got , err := env .adapter .GetCustomerByUsageAttribution (t .Context (), customer.GetCustomerByUsageAttributionInput {
401+ Namespace : ns ,
402+ Key : "cust-key" ,
403+ })
404+ require .NoError (t , err )
405+ require .NotNil (t , got )
406+ assert .Equal (t , id , got .ID )
407+ require .NotNil (t , got .UsageAttribution )
408+ assert .Equal (t , []string {"subj-1" }, got .UsageAttribution .SubjectKeys )
409+ })
410+
411+ t .Run ("MatchBySubjectKey" , func (t * testing.T ) {
412+ env := newTestEnv (t )
413+ ns := ulid .Make ().String ()
414+ id := env .seedCustomerWithKey (ns , "cust-key" , "subj-1" , "subj-2" )
415+
416+ got , err := env .adapter .GetCustomerByUsageAttribution (t .Context (), customer.GetCustomerByUsageAttributionInput {
417+ Namespace : ns ,
418+ Key : "subj-2" ,
419+ })
420+ require .NoError (t , err )
421+ require .NotNil (t , got )
422+ assert .Equal (t , id , got .ID )
423+ require .NotNil (t , got .UsageAttribution )
424+ assert .Equal (t , []string {"subj-1" , "subj-2" }, got .UsageAttribution .SubjectKeys )
425+ })
426+
427+ t .Run ("CustomerKeyTakesPriorityOverAnotherCustomerSubjectKey" , func (t * testing.T ) {
428+ env := newTestEnv (t )
429+ ns := ulid .Make ().String ()
430+ customerKeyID := env .seedCustomerWithKey (ns , "shared-key" , "customer-key-subject" )
431+ _ = env .seedCustomerWithKey (ns , "subject-owner" , "shared-key" )
432+
433+ got , err := env .adapter .GetCustomerByUsageAttribution (t .Context (), customer.GetCustomerByUsageAttributionInput {
434+ Namespace : ns ,
435+ Key : "shared-key" ,
436+ })
437+ require .NoError (t , err )
438+ require .NotNil (t , got )
439+ assert .Equal (t , customerKeyID , got .ID , "a direct customer-key match must take priority over a subject-key match" )
440+ })
441+
442+ t .Run ("CustomerMatchedByOwnKeyAndSubjectKeyReturnedOnce" , func (t * testing.T ) {
443+ env := newTestEnv (t )
444+ ns := ulid .Make ().String ()
445+ id := env .seedCustomerWithKey (ns , "shared-key" , "shared-key" )
446+
447+ got , err := env .adapter .GetCustomerByUsageAttribution (t .Context (), customer.GetCustomerByUsageAttributionInput {
448+ Namespace : ns ,
449+ Key : "shared-key" ,
450+ })
451+ require .NoError (t , err )
452+ require .NotNil (t , got )
453+ assert .Equal (t , id , got .ID )
454+ })
455+
456+ t .Run ("SoftDeletedCustomerExcluded" , func (t * testing.T ) {
457+ env := newTestEnv (t )
458+ ns := ulid .Make ().String ()
459+ id := env .seedCustomerWithKey (ns , "cust-key" , "subj-1" )
460+ _ = freezeTime (t , time .Now ())
461+
462+ require .NoError (t , env .adapter .DeleteCustomer (t .Context (), customer.DeleteCustomerInput {
463+ Namespace : ns ,
464+ ID : id ,
465+ }))
466+
467+ for _ , key := range []string {"cust-key" , "subj-1" } {
468+ _ , err := env .adapter .GetCustomerByUsageAttribution (t .Context (), customer.GetCustomerByUsageAttributionInput {
469+ Namespace : ns ,
470+ Key : key ,
471+ })
472+ require .Error (t , err )
473+ assert .True (t , models .IsGenericNotFoundError (err ))
474+ }
475+ })
476+
477+ t .Run ("SubjectDeletedInPastExcluded" , func (t * testing.T ) {
478+ env := newTestEnv (t )
479+ ns := ulid .Make ().String ()
480+ id := env .seedCustomerWithKey (ns , "cust-key" , "subj-1" )
481+ now := freezeTime (t , time .Now ())
482+
483+ _ , err := env .db .CustomerSubjects .Update ().
484+ Where (
485+ customersubjectsdb .Namespace (ns ),
486+ customersubjectsdb .CustomerID (id ),
487+ customersubjectsdb .SubjectKey ("subj-1" ),
488+ ).
489+ SetDeletedAt (now .Add (- time .Minute )).
490+ Save (t .Context ())
491+ require .NoError (t , err )
492+
493+ _ , err = env .adapter .GetCustomerByUsageAttribution (t .Context (), customer.GetCustomerByUsageAttributionInput {
494+ Namespace : ns ,
495+ Key : "subj-1" ,
496+ })
497+ require .Error (t , err )
498+ assert .True (t , models .IsGenericNotFoundError (err ))
499+ })
500+
501+ t .Run ("SubjectDeletedAtLookupTimeExcluded" , func (t * testing.T ) {
502+ env := newTestEnv (t )
503+ ns := ulid .Make ().String ()
504+ id := env .seedCustomerWithKey (ns , "cust-key" , "subj-1" )
505+ now := freezeTime (t , time .Now ())
506+
507+ _ , err := env .db .CustomerSubjects .Update ().
508+ Where (
509+ customersubjectsdb .Namespace (ns ),
510+ customersubjectsdb .CustomerID (id ),
511+ customersubjectsdb .SubjectKey ("subj-1" ),
512+ ).
513+ SetDeletedAt (now ).
514+ Save (t .Context ())
515+ require .NoError (t , err )
516+
517+ _ , err = env .adapter .GetCustomerByUsageAttribution (t .Context (), customer.GetCustomerByUsageAttributionInput {
518+ Namespace : ns ,
519+ Key : "subj-1" ,
520+ })
521+ require .Error (t , err )
522+ assert .True (t , models .IsGenericNotFoundError (err ))
523+ })
524+
525+ t .Run ("SubjectDeletedInFutureIncluded" , func (t * testing.T ) {
526+ env := newTestEnv (t )
527+ ns := ulid .Make ().String ()
528+ id := env .seedCustomerWithKey (ns , "cust-key" , "subj-1" )
529+ now := freezeTime (t , time .Now ())
530+
531+ _ , err := env .db .CustomerSubjects .Update ().
532+ Where (
533+ customersubjectsdb .Namespace (ns ),
534+ customersubjectsdb .CustomerID (id ),
535+ customersubjectsdb .SubjectKey ("subj-1" ),
536+ ).
537+ SetDeletedAt (now .Add (time .Minute )).
538+ Save (t .Context ())
539+ require .NoError (t , err )
540+
541+ got , err := env .adapter .GetCustomerByUsageAttribution (t .Context (), customer.GetCustomerByUsageAttributionInput {
542+ Namespace : ns ,
543+ Key : "subj-1" ,
544+ })
545+ require .NoError (t , err )
546+ require .NotNil (t , got )
547+ assert .Equal (t , id , got .ID )
548+ })
549+
550+ t .Run ("NamespaceIsolation" , func (t * testing.T ) {
551+ env := newTestEnv (t )
552+ nsA := ulid .Make ().String ()
553+ nsB := ulid .Make ().String ()
554+ idA := env .seedCustomerWithKey (nsA , "shared-customer-key" , "shared-subject-key" )
555+ _ = env .seedCustomerWithKey (nsB , "shared-customer-key" , "shared-subject-key" )
556+
557+ for _ , key := range []string {"shared-customer-key" , "shared-subject-key" } {
558+ got , err := env .adapter .GetCustomerByUsageAttribution (t .Context (), customer.GetCustomerByUsageAttributionInput {
559+ Namespace : nsA ,
560+ Key : key ,
561+ })
562+ require .NoError (t , err )
563+ require .NotNil (t , got )
564+ assert .Equal (t , idA , got .ID )
565+ }
566+ })
567+
568+ t .Run ("UnknownKeyReturnsNotFound" , func (t * testing.T ) {
569+ env := newTestEnv (t )
570+ ns := ulid .Make ().String ()
571+ _ = env .seedCustomerWithKey (ns , "cust-key" , "subj-1" )
572+
573+ _ , err := env .adapter .GetCustomerByUsageAttribution (t .Context (), customer.GetCustomerByUsageAttributionInput {
574+ Namespace : ns ,
575+ Key : "missing" ,
576+ })
577+ require .Error (t , err )
578+ assert .True (t , models .IsGenericNotFoundError (err ))
579+ })
580+ }
581+
394582func TestGetCustomersByUsageAttribution (t * testing.T ) {
395583 t .Run ("MatchByCustomerKey" , func (t * testing.T ) {
396584 env := newTestEnv (t )
0 commit comments