@@ -2,6 +2,7 @@ package service_test
22
33import (
44 "context"
5+ "errors"
56 "testing"
67 "time"
78
@@ -885,6 +886,119 @@ func TestPlanTaxCodeBackfill(t *testing.T) {
885886 })
886887}
887888
889+ func TestPlanPublishRejectsDeletedTaxCode (t * testing.T ) {
890+ MonthPeriod := datetime .MustParseDuration (t , "P1M" )
891+
892+ ctx , cancel := context .WithCancel (context .Background ())
893+ defer cancel ()
894+
895+ env := pctestutils .NewTestEnv (t )
896+ t .Cleanup (func () { env .Close (t ) })
897+ env .DBSchemaMigrate (t )
898+
899+ namespace := pctestutils .NewTestNamespace (t )
900+
901+ // given:
902+ // - meters and a feature are provisioned
903+ // - a tax code is created and then deleted, leaving a dangling reference
904+ // - a draft plan has a rate card referencing that (now-deleted) tax code
905+
906+ err := env .Meter .ReplaceMeters (ctx , pctestutils .NewTestMeters (t , namespace ))
907+ require .NoError (t , err )
908+
909+ result , err := env .Meter .ListMeters (ctx , meter.ListMetersParams {
910+ Page : pagination.Page {
911+ PageSize : 1000 ,
912+ PageNumber : 1 ,
913+ },
914+ Namespace : namespace ,
915+ })
916+ require .NoError (t , err )
917+ require .NotEmpty (t , result .Items )
918+
919+ features := make ([]feature.Feature , 0 , len (result .Items ))
920+ for _ , m := range result .Items {
921+ feat , err := env .Feature .CreateFeature (ctx , pctestutils .NewTestFeatureFromMeter (t , & m ))
922+ require .NoError (t , err )
923+ features = append (features , feat )
924+ }
925+
926+ // Provision organization-default tax codes so DeleteTaxCode can proceed past the org-defaults check.
927+ invoicingTC , err := env .TaxCode .CreateTaxCode (ctx , taxcode.CreateTaxCodeInput {
928+ Namespace : namespace ,
929+ Key : "org-default-invoicing" ,
930+ Name : "Org Default Invoicing" ,
931+ AppMappings : taxcode.TaxCodeAppMappings {
932+ {AppType : app .AppTypeStripe , TaxCode : "txcd_00000001" },
933+ },
934+ })
935+ require .NoError (t , err )
936+
937+ creditGrantTC , err := env .TaxCode .CreateTaxCode (ctx , taxcode.CreateTaxCodeInput {
938+ Namespace : namespace ,
939+ Key : "org-default-credit-grant" ,
940+ Name : "Org Default Credit Grant" ,
941+ AppMappings : taxcode.TaxCodeAppMappings {
942+ {AppType : app .AppTypeStripe , TaxCode : "txcd_00000002" },
943+ },
944+ })
945+ require .NoError (t , err )
946+
947+ _ , err = env .TaxCode .UpsertOrganizationDefaultTaxCodes (ctx , taxcode.UpsertOrganizationDefaultTaxCodesInput {
948+ Namespace : namespace ,
949+ InvoicingTaxCodeID : invoicingTC .ID ,
950+ CreditGrantTaxCodeID : creditGrantTC .ID ,
951+ })
952+ require .NoError (t , err )
953+
954+ // Create a tax code with a Stripe app mapping so it resolves at create time.
955+ tcEntity , err := env .TaxCode .CreateTaxCode (ctx , taxcode.CreateTaxCodeInput {
956+ Namespace : namespace ,
957+ Key : "stripe_txcd_40000001" ,
958+ Name : "txcd_40000001" ,
959+ AppMappings : taxcode.TaxCodeAppMappings {
960+ {AppType : app .AppTypeStripe , TaxCode : "txcd_40000001" },
961+ },
962+ })
963+ require .NoError (t , err )
964+
965+ taxCodeID := tcEntity .ID
966+
967+ // Create a DRAFT plan with a rate card referencing the tax code.
968+ input := newTestPlanInput (t , namespace , newTestFlatRateCard (features [0 ], & productcatalog.TaxConfig {
969+ TaxCodeID : lo .ToPtr (taxCodeID ),
970+ }, & MonthPeriod ))
971+ input .Key = "publish-deleted-taxcode"
972+ input .Name = "Publish Deleted TaxCode"
973+
974+ p , err := env .Plan .CreatePlan (ctx , input )
975+ require .NoError (t , err )
976+
977+ // Delete the tax code — the plan-reference delete hook is NOT registered in pctestutils,
978+ // so this succeeds and leaves a dangling reference (intended for this test).
979+ err = env .TaxCode .DeleteTaxCode (ctx , taxcode.DeleteTaxCodeInput {
980+ NamespacedID : models.NamespacedID {Namespace : namespace , ID : taxCodeID },
981+ })
982+ require .NoError (t , err )
983+
984+ // when: publishing the plan
985+ publishAt := time .Now ().Truncate (time .Microsecond )
986+ _ , err = env .Plan .PublishPlan (ctx , plan.PublishPlanInput {
987+ NamespacedID : p .NamespacedID ,
988+ EffectivePeriod : productcatalog.EffectivePeriod {
989+ EffectiveFrom : & publishAt ,
990+ EffectiveTo : nil ,
991+ },
992+ })
993+
994+ // then: publish fails with a rate-card tax-code-not-found validation issue
995+ require .Error (t , err )
996+
997+ var vi models.ValidationIssue
998+ require .True (t , errors .As (err , & vi ), "expected ValidationIssue wrapping ErrCodeRateCardTaxCodeNotFound, got %T: %v" , err , err )
999+ require .Equal (t , productcatalog .ErrCodeRateCardTaxCodeNotFound , vi .Code ())
1000+ }
1001+
8881002func TestPlanWithAddonTaxCode (t * testing.T ) {
8891003 MonthPeriod := datetime .MustParseDuration (t , "P1M" )
8901004
0 commit comments