|
1 | 1 | package hooks_test |
2 | 2 |
|
3 | 3 | import ( |
4 | | - "context" |
5 | 4 | "testing" |
6 | 5 |
|
| 6 | + decimal "github.com/alpacahq/alpacadecimal" |
| 7 | + "github.com/samber/lo" |
7 | 8 | "github.com/stretchr/testify/require" |
8 | 9 |
|
9 | | - "github.com/openmeterio/openmeter/openmeter/productcatalog/addon" |
| 10 | + "github.com/openmeterio/openmeter/openmeter/app" |
| 11 | + "github.com/openmeterio/openmeter/openmeter/productcatalog" |
| 12 | + pctestutils "github.com/openmeterio/openmeter/openmeter/productcatalog/testutils" |
10 | 13 | "github.com/openmeterio/openmeter/openmeter/taxcode" |
11 | 14 | "github.com/openmeterio/openmeter/openmeter/taxcode/service/hooks" |
12 | 15 | "github.com/openmeterio/openmeter/pkg/models" |
13 | | - "github.com/openmeterio/openmeter/pkg/pagination" |
14 | 16 | ) |
15 | 17 |
|
16 | | -// stubAddonService implements addon.Service for testing the addon hook. |
17 | | -// Only ListAddons has real behavior; all other methods panic. |
18 | | -type stubAddonService struct { |
19 | | - listResult pagination.Result[addon.Addon] |
20 | | - listErr error |
21 | | - lastInput addon.ListAddonsInput |
22 | | -} |
23 | | - |
24 | | -func (s *stubAddonService) ListAddons(ctx context.Context, params addon.ListAddonsInput) (pagination.Result[addon.Addon], error) { |
25 | | - s.lastInput = params |
26 | | - return s.listResult, s.listErr |
27 | | -} |
28 | | - |
29 | | -func (s *stubAddonService) CreateAddon(_ context.Context, _ addon.CreateAddonInput) (*addon.Addon, error) { |
30 | | - panic("not implemented") |
31 | | -} |
32 | | - |
33 | | -func (s *stubAddonService) DeleteAddon(_ context.Context, _ addon.DeleteAddonInput) error { |
34 | | - panic("not implemented") |
35 | | -} |
36 | | - |
37 | | -func (s *stubAddonService) GetAddon(_ context.Context, _ addon.GetAddonInput) (*addon.Addon, error) { |
38 | | - panic("not implemented") |
39 | | -} |
| 18 | +func TestAddonHookPreDelete(t *testing.T) { |
| 19 | + // Setup real services backed by Postgres. |
| 20 | + env := pctestutils.NewTestEnv(t) |
| 21 | + t.Cleanup(func() { env.Close(t) }) |
| 22 | + env.DBSchemaMigrate(t) |
40 | 23 |
|
41 | | -func (s *stubAddonService) UpdateAddon(_ context.Context, _ addon.UpdateAddonInput) (*addon.Addon, error) { |
42 | | - panic("not implemented") |
43 | | -} |
| 24 | + // Register the addon hook on the real taxcode service. |
| 25 | + addonHook, err := hooks.NewAddonHook(hooks.AddonHookConfig{AddonService: env.Addon}) |
| 26 | + require.NoError(t, err) |
| 27 | + env.TaxCode.RegisterHooks(addonHook) |
44 | 28 |
|
45 | | -func (s *stubAddonService) PublishAddon(_ context.Context, _ addon.PublishAddonInput) (*addon.Addon, error) { |
46 | | - panic("not implemented") |
47 | | -} |
| 29 | + ns := pctestutils.NewTestNamespace(t) |
48 | 30 |
|
49 | | -func (s *stubAddonService) ArchiveAddon(_ context.Context, _ addon.ArchiveAddonInput) (*addon.Addon, error) { |
50 | | - panic("not implemented") |
51 | | -} |
52 | | - |
53 | | -func (s *stubAddonService) NextAddon(_ context.Context, _ addon.NextAddonInput) (*addon.Addon, error) { |
54 | | - panic("not implemented") |
55 | | -} |
56 | | - |
57 | | -var _ addon.Service = (*stubAddonService)(nil) |
58 | | - |
59 | | -const addonTestTaxCodeID = "01234567890123456789012346" |
60 | | - |
61 | | -func TestAddonHook_PreDelete(t *testing.T) { |
62 | | - tc := &taxcode.TaxCode{ |
63 | | - NamespacedID: models.NamespacedID{ |
64 | | - Namespace: "test-ns", |
65 | | - ID: addonTestTaxCodeID, |
66 | | - }, |
67 | | - } |
| 31 | + // Provision organization-default tax codes so DeleteTaxCode can proceed past |
| 32 | + // the org-defaults check and reach the pre-delete hook. |
| 33 | + setupNamespaceDefaults(t, env, ns) |
68 | 34 |
|
69 | 35 | t.Run("blocks deletion when an add-on references the tax code", func(t *testing.T) { |
70 | | - // given: an addon service that returns one matching add-on |
71 | | - stub := &stubAddonService{ |
72 | | - listResult: pagination.Result[addon.Addon]{ |
73 | | - Items: []addon.Addon{ |
74 | | - {ManagedModel: models.ManagedModel{}, NamespacedID: models.NamespacedID{ID: "addon-abc"}}, |
75 | | - }, |
76 | | - TotalCount: 1, |
| 36 | + // given: a tax code that an add-on will reference |
| 37 | + referenced, err := env.TaxCode.CreateTaxCode(t.Context(), taxcode.CreateTaxCodeInput{ |
| 38 | + Namespace: ns, |
| 39 | + Key: "addon-referenced", |
| 40 | + Name: "Referenced Tax Code", |
| 41 | + AppMappings: taxcode.TaxCodeAppMappings{ |
| 42 | + {AppType: app.AppTypeStripe, TaxCode: "txcd_20000003"}, |
77 | 43 | }, |
78 | | - } |
| 44 | + }) |
| 45 | + require.NoError(t, err) |
79 | 46 |
|
80 | | - hook, err := hooks.NewAddonHook(hooks.AddonHookConfig{AddonService: stub}) |
| 47 | + // given: an add-on whose rate card references the tax code |
| 48 | + addonInput := pctestutils.NewTestAddon(t, ns, |
| 49 | + &productcatalog.FlatFeeRateCard{ |
| 50 | + RateCardMeta: productcatalog.RateCardMeta{ |
| 51 | + Key: "rc-1", |
| 52 | + Name: "RC 1", |
| 53 | + TaxConfig: &productcatalog.TaxConfig{ |
| 54 | + TaxCodeID: lo.ToPtr(referenced.ID), |
| 55 | + }, |
| 56 | + Price: productcatalog.NewPriceFrom(productcatalog.FlatPrice{ |
| 57 | + Amount: decimal.NewFromInt(0), |
| 58 | + PaymentTerm: productcatalog.InArrearsPaymentTerm, |
| 59 | + }), |
| 60 | + }, |
| 61 | + }, |
| 62 | + ) |
| 63 | + addonInput.Key = "addon-with-taxcode" |
| 64 | + _, err = env.Addon.CreateAddon(t.Context(), addonInput) |
81 | 65 | require.NoError(t, err) |
82 | 66 |
|
83 | | - // when: PreDelete is called |
84 | | - err = hook.PreDelete(t.Context(), tc) |
| 67 | + // when: attempting to delete the referenced tax code |
| 68 | + err = env.TaxCode.DeleteTaxCode(t.Context(), taxcode.DeleteTaxCodeInput{ |
| 69 | + NamespacedID: models.NamespacedID{Namespace: ns, ID: referenced.ID}, |
| 70 | + }) |
85 | 71 |
|
86 | 72 | // then: an error is returned and it is a TaxCodeReferencedByAddon error |
87 | 73 | require.Error(t, err) |
88 | 74 | require.True(t, taxcode.IsTaxCodeReferencedByAddonError(err), |
89 | 75 | "expected TaxCodeReferencedByAddon error, got: %v", err) |
90 | | - |
91 | | - // and: the stub received a ListAddonsInput whose TaxCodes.In contains the tax code id |
92 | | - require.NotNil(t, stub.lastInput.TaxCodes) |
93 | | - require.NotNil(t, stub.lastInput.TaxCodes.In) |
94 | | - require.Contains(t, *stub.lastInput.TaxCodes.In, addonTestTaxCodeID) |
95 | 76 | }) |
96 | 77 |
|
97 | 78 | t.Run("allows deletion when no add-on references the tax code", func(t *testing.T) { |
98 | | - // given: an addon service that returns no matching add-ons |
99 | | - stub := &stubAddonService{ |
100 | | - listResult: pagination.Result[addon.Addon]{ |
101 | | - Items: []addon.Addon{}, |
102 | | - TotalCount: 0, |
| 79 | + // given: a tax code that no add-on references |
| 80 | + unreferenced, err := env.TaxCode.CreateTaxCode(t.Context(), taxcode.CreateTaxCodeInput{ |
| 81 | + Namespace: ns, |
| 82 | + Key: "addon-unreferenced", |
| 83 | + Name: "Unreferenced Tax Code", |
| 84 | + AppMappings: taxcode.TaxCodeAppMappings{ |
| 85 | + {AppType: app.AppTypeStripe, TaxCode: "txcd_20000004"}, |
103 | 86 | }, |
104 | | - } |
105 | | - |
106 | | - hook, err := hooks.NewAddonHook(hooks.AddonHookConfig{AddonService: stub}) |
| 87 | + }) |
107 | 88 | require.NoError(t, err) |
108 | 89 |
|
109 | | - // when: PreDelete is called |
110 | | - err = hook.PreDelete(t.Context(), tc) |
| 90 | + // when: deleting the unreferenced tax code |
| 91 | + err = env.TaxCode.DeleteTaxCode(t.Context(), taxcode.DeleteTaxCodeInput{ |
| 92 | + NamespacedID: models.NamespacedID{Namespace: ns, ID: unreferenced.ID}, |
| 93 | + }) |
111 | 94 |
|
112 | 95 | // then: no error is returned |
113 | 96 | require.NoError(t, err) |
|
0 commit comments