Skip to content

Commit 0f67a4e

Browse files
authored
Merge branch 'main' into feat/subscription-addon-get
2 parents 9b390ba + de21763 commit 0f67a4e

22 files changed

Lines changed: 686 additions & 312 deletions

File tree

.github/workflows/analysis-scorecard.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,6 @@ jobs:
4242
retention-days: 5
4343

4444
- name: Upload results to GitHub Security tab
45-
uses: github/codeql-action/upload-sarif@95e58e9a2cdfd71adc6e0353d5c52f41a045d225 # v3.29.5
45+
uses: github/codeql-action/upload-sarif@e46ed2cbd01164d986452f91f178727624ae40d7 # v3.29.5
4646
with:
4747
sarif_file: results.sarif

.github/workflows/ci.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -200,7 +200,7 @@ jobs:
200200
persist-credentials: false
201201

202202
- name: Set up pnpm
203-
uses: pnpm/action-setup@903f9c1a6ebcba6cf41d87230be49611ac97822e # v6
203+
uses: pnpm/action-setup@8912a9102ac27614460f54aedde9e1e7f9aec20d # v6
204204
with:
205205
package_json_file: api/client/javascript/package.json
206206

.github/workflows/npm-release.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ jobs:
4747
# perform the OIDC handshake. setup-node provides a current npm and
4848
# writes a registry-scoped .npmrc with no _authToken.
4949
- name: Set up pnpm
50-
uses: pnpm/action-setup@903f9c1a6ebcba6cf41d87230be49611ac97822e # v6
50+
uses: pnpm/action-setup@8912a9102ac27614460f54aedde9e1e7f9aec20d # v6
5151
with:
5252
package_json_file: api/client/javascript/package.json
5353

e2e/addons_v3_test.go

Lines changed: 83 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
"net/http"
55
"testing"
66

7+
"github.com/samber/lo"
78
"github.com/stretchr/testify/assert"
89
"github.com/stretchr/testify/require"
910

@@ -132,11 +133,62 @@ func TestV3Addon(t *testing.T) {
132133
func TestV3AddonMixedRateCardRoundTrip(t *testing.T) {
133134
c := newV3Client(t)
134135

136+
eventTypes := []string{
137+
uniqueKey("sanity_event"),
138+
uniqueKey("sanity_event"),
139+
}
140+
141+
meterKeys := []string{
142+
uniqueKey("sanity_meter"),
143+
uniqueKey("sanity_meter"),
144+
}
145+
146+
meters := make([]apiv3.Meter, 0, len(meterKeys))
147+
148+
for i := range meterKeys {
149+
valueProperty := "$.value"
150+
151+
status, m, problem := c.CreateMeter(apiv3.CreateMeterRequest{
152+
Key: meterKeys[i],
153+
Name: "Test Meter " + meterKeys[i],
154+
Aggregation: apiv3.MeterAggregationSum,
155+
EventType: eventTypes[i],
156+
ValueProperty: &valueProperty,
157+
})
158+
require.Equalf(t, http.StatusCreated, status, "problem: %+v", problem)
159+
require.NotNil(t, m)
160+
require.NotEmpty(t, m.Id)
161+
162+
meters = append(meters, *m)
163+
}
164+
165+
featureKeys := []string{
166+
uniqueKey("mix_unit"),
167+
uniqueKey("mix_graduated"),
168+
}
169+
170+
features := make([]apiv3.Feature, 0, len(featureKeys))
171+
172+
for i := range featureKeys {
173+
status, f, problem := c.CreateFeature(apiv3.CreateFeatureRequest{
174+
Key: featureKeys[i],
175+
Name: "Test Feature " + featureKeys[i],
176+
Meter: &apiv3.FeatureMeterReference{
177+
Id: meters[i].Id,
178+
},
179+
})
180+
require.Equal(t, http.StatusCreated, status, "problem: %+v", problem)
181+
require.NotNil(t, f)
182+
require.NotEmpty(t, f.Id)
183+
184+
features = append(features, *f)
185+
}
186+
135187
flat := validFlatRateCard("mix_flat")
136-
unit := validUnitRateCard("mix_unit")
188+
unit := validUnitRateCard(features[0])
137189
percent := float32(10)
138190
unit.Discounts = &apiv3.BillingRateCardDiscounts{Percentage: &percent}
139-
graduated := validGraduatedRateCard("mix_graduated")
191+
graduated := validGraduatedRateCard(features[1])
140192

141193
body := validAddonRequest("mixed_rc")
142194
body.RateCards = []apiv3.BillingRateCard{flat, unit, graduated}
@@ -283,6 +335,34 @@ func TestV3AddonPaymentTermPriceCompatibility(t *testing.T) {
283335
// Only the "happy" rows are exercised — they confirm flat and unit prices are
284336
// accepted in their respective valid combinations.
285337
func TestV3AddonInstanceTypePriceCompatibility(t *testing.T) {
338+
c := newV3Client(t)
339+
340+
meterKey := uniqueKey("single_unit")
341+
342+
status, m, problem := c.CreateMeter(apiv3.CreateMeterRequest{
343+
Key: meterKey,
344+
Name: "Test Meter " + meterKey,
345+
Aggregation: apiv3.MeterAggregationSum,
346+
EventType: uniqueKey("sanity_event"),
347+
ValueProperty: lo.ToPtr("$.value"),
348+
})
349+
require.Equal(t, http.StatusCreated, status, "problem: %+v", problem)
350+
require.NotNil(t, m)
351+
require.NotEmpty(t, m.Id)
352+
353+
featureKey := uniqueKey("single_unit")
354+
355+
status, f, problem := c.CreateFeature(apiv3.CreateFeatureRequest{
356+
Key: featureKey,
357+
Name: "Test Feature " + featureKey,
358+
Meter: &apiv3.FeatureMeterReference{
359+
Id: m.Id,
360+
},
361+
})
362+
require.Equal(t, http.StatusCreated, status, "problem: %+v", problem)
363+
require.NotNil(t, f)
364+
require.NotEmpty(t, f.Id)
365+
286366
cases := []struct {
287367
name string
288368
instanceType apiv3.AddonInstanceType
@@ -298,15 +378,13 @@ func TestV3AddonInstanceTypePriceCompatibility(t *testing.T) {
298378
{
299379
name: "single + unit rate card → 201",
300380
instanceType: apiv3.AddonInstanceTypeSingle,
301-
rateCard: validUnitRateCard("single_unit"),
381+
rateCard: validUnitRateCard(*f),
302382
expectedStatus: http.StatusCreated,
303383
},
304384
}
305385

306386
for _, tc := range cases {
307387
t.Run(tc.name, func(t *testing.T) {
308-
c := newV3Client(t)
309-
310388
body := validAddonRequest("instance_type_price")
311389
body.InstanceType = tc.instanceType
312390
body.RateCards = []apiv3.BillingRateCard{tc.rateCard}

e2e/e2e_test.go

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1560,7 +1560,9 @@ func TestCredit(t *testing.T) {
15601560
require.Len(t, *grantListResp.JSON200, 1)
15611561

15621562
// Get feature
1563-
featureListResp, err := client.ListFeaturesWithResponse(context.Background(), nil)
1563+
featureListResp, err := client.ListFeaturesWithResponse(t.Context(), &api.ListFeaturesParams{
1564+
MeterSlug: &[]string{meterSlug},
1565+
})
15641566
require.NoError(t, err)
15651567
require.Equal(t, http.StatusOK, featureListResp.StatusCode())
15661568
require.NotNil(t, featureListResp.JSON200)

e2e/productcatalog_smoke_v3_test.go

Lines changed: 41 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -30,49 +30,63 @@ import (
3030
func TestV3ProductCatalogSmoke(t *testing.T) {
3131
c := newV3Client(t)
3232

33-
meterKey := uniqueKey("sanity_meter")
34-
eventType := uniqueKey("sanity_event")
35-
featureKey := uniqueKey("sanity_feature")
33+
eventTypes := []string{
34+
uniqueKey("sanity_event"),
35+
uniqueKey("sanity_event"),
36+
}
3637

37-
var (
38-
meterID string
39-
featureID string
40-
planID string
41-
addonID string
42-
planAddonID string
43-
phaseKey string
44-
)
38+
meterKeys := []string{
39+
uniqueKey("sanity_meter"),
40+
uniqueKey("sanity_meter"),
41+
}
42+
43+
meters := make([]apiv3.Meter, 0, len(meterKeys))
4544

46-
t.Run("Should create a meter", func(t *testing.T) {
45+
for i := range meterKeys {
4746
valueProperty := "$.value"
47+
4848
status, m, problem := c.CreateMeter(apiv3.CreateMeterRequest{
49-
Key: meterKey,
50-
Name: "Test Meter " + meterKey,
49+
Key: meterKeys[i],
50+
Name: "Test Meter " + meterKeys[i],
5151
Aggregation: apiv3.MeterAggregationSum,
52-
EventType: eventType,
52+
EventType: eventTypes[i],
5353
ValueProperty: &valueProperty,
5454
})
5555
require.Equal(t, http.StatusCreated, status, "problem: %+v", problem)
5656
require.NotNil(t, m)
5757
require.NotEmpty(t, m.Id)
58-
meterID = m.Id
59-
})
6058

61-
t.Run("Should create a feature bound to the meter", func(t *testing.T) {
62-
require.NotEmpty(t, meterID)
59+
meters = append(meters, *m)
60+
}
61+
62+
featureKeys := []string{
63+
uniqueKey("sanity_feature"),
64+
uniqueKey("sanity_feature"),
65+
}
6366

67+
features := make([]apiv3.Feature, 0, len(featureKeys))
68+
69+
for i := range featureKeys {
6470
status, f, problem := c.CreateFeature(apiv3.CreateFeatureRequest{
65-
Key: featureKey,
66-
Name: "Test Feature " + featureKey,
71+
Key: featureKeys[i],
72+
Name: "Test Feature " + featureKeys[i],
6773
Meter: &apiv3.FeatureMeterReference{
68-
Id: meterID,
74+
Id: meters[i].Id,
6975
},
7076
})
7177
require.Equal(t, http.StatusCreated, status, "problem: %+v", problem)
7278
require.NotNil(t, f)
7379
require.NotEmpty(t, f.Id)
74-
featureID = f.Id
75-
})
80+
81+
features = append(features, *f)
82+
}
83+
84+
var (
85+
planID string
86+
addonID string
87+
planAddonID string
88+
phaseKey string
89+
)
7690

7791
t.Run("Should create a draft plan with a single flat rate card", func(t *testing.T) {
7892
body := validPlanRequest("sanity_plan")
@@ -89,21 +103,15 @@ func TestV3ProductCatalogSmoke(t *testing.T) {
89103
})
90104

91105
t.Run("Should update the plan to carry flat + usage + graduated rate cards", func(t *testing.T) {
92-
t.Skip("Skip this test as it does not use rate cards with features properly")
93-
94-
require.NotEmpty(t, planID)
95-
require.NotEmpty(t, phaseKey)
96-
require.NotEmpty(t, featureID)
97-
98106
// Three different rate card shapes on one phase. The flat fee is
99107
// in_advance, both usage-based ones are in_arrears (unit/graduated
100108
// prices cannot be in_advance). Only the unit one carries a feature
101109
// reference here — the graduated case follows the addon mixed
102110
// round-trip pattern (no feature) to keep this iteration close to
103111
// known-good shapes.
104112
flat := validFlatRateCard("sanity_flat")
105-
usage := validUsageRateCard("sanity_usage", featureID)
106-
graduated := validGraduatedRateCard("sanity_graduated")
113+
usage := validUnitRateCard(features[0])
114+
graduated := validGraduatedRateCard(features[1])
107115

108116
update := apiv3.UpsertPlanRequest{
109117
Name: "Sanity Plan",
@@ -129,16 +137,14 @@ func TestV3ProductCatalogSmoke(t *testing.T) {
129137
}
130138
require.NotNil(t, usageRC, "usage rate card missing after update")
131139
require.NotNil(t, usageRC.Feature, "usage rate card lost its feature binding after update")
132-
assert.Equal(t, featureID, usageRC.Feature.Id)
140+
assert.Equal(t, usage.Feature.Id, usageRC.Feature.Id)
133141
})
134142

135143
// Track the three valid rate cards across the invalid-loop subtests so
136144
// "remove defective" PUTs can rebuild the phase from the same baseline.
137145
var validRateCards []apiv3.BillingRateCard
138146

139147
t.Run("Should add a defective rate card and surface validation_errors", func(t *testing.T) {
140-
t.Skip("Skip this test as it does not use rate cards with features properly")
141-
142148
require.NotEmpty(t, planID)
143149
require.NotEmpty(t, phaseKey)
144150

@@ -190,8 +196,6 @@ func TestV3ProductCatalogSmoke(t *testing.T) {
190196
})
191197

192198
t.Run("Should remove the defective rate card and clear validation_errors", func(t *testing.T) {
193-
t.Skip("Skip this test as it does not use rate cards with features properly")
194-
195199
require.NotEmpty(t, planID)
196200
require.NotEmpty(t, phaseKey)
197201
require.NotEmpty(t, validRateCards)

e2e/v3helpers_test.go

Lines changed: 8 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -376,7 +376,7 @@ func validFlatRateCard(keyPrefix string) apiv3.BillingRateCard {
376376
// validUnitRateCard returns a usage-based unit-priced rate card. Unit prices
377377
// cannot use payment_term=in_advance (that's flat-only), so this uses
378378
// in_arrears.
379-
func validUnitRateCard(keyPrefix string) apiv3.BillingRateCard {
379+
func validUnitRateCard(f apiv3.Feature) apiv3.BillingRateCard {
380380
cadence := apiv3.ISO8601Duration("P1M")
381381
term := apiv3.BillingPricePaymentTermInArrears
382382

@@ -389,27 +389,18 @@ func validUnitRateCard(keyPrefix string) apiv3.BillingRateCard {
389389
}
390390

391391
return apiv3.BillingRateCard{
392-
Key: uniqueKey(keyPrefix),
393-
Name: "Test Unit Rate Card " + keyPrefix,
392+
Key: f.Key,
393+
Name: "Test Unit Rate Card " + f.Key,
394394
Price: price,
395395
BillingCadence: &cadence,
396396
PaymentTerm: &term,
397+
Feature: &apiv3.FeatureReferenceItem{Id: f.Id},
397398
}
398399
}
399400

400-
// validUsageRateCard returns a unit-priced rate card bound to the given
401-
// feature ID — the shape needed when a plan/addon meters usage against a
402-
// metered feature. Reuses validUnitRateCard's price + cadence + payment_term
403-
// (unit prices must use in_arrears).
404-
func validUsageRateCard(keyPrefix, featureID string) apiv3.BillingRateCard {
405-
rc := validUnitRateCard(keyPrefix)
406-
rc.Feature = &apiv3.FeatureReferenceItem{Id: featureID}
407-
return rc
408-
}
409-
410401
// validGraduatedRateCard returns a graduated tiered rate card with two tiers:
411402
// 0–100 units at $0.10/unit and 100+ units at $0.05/unit.
412-
func validGraduatedRateCard(keyPrefix string) apiv3.BillingRateCard {
403+
func validGraduatedRateCard(f apiv3.Feature) apiv3.BillingRateCard {
413404
cadence := apiv3.ISO8601Duration("P1M")
414405
term := apiv3.BillingPricePaymentTermInArrears
415406

@@ -437,11 +428,12 @@ func validGraduatedRateCard(keyPrefix string) apiv3.BillingRateCard {
437428
}
438429

439430
return apiv3.BillingRateCard{
440-
Key: uniqueKey(keyPrefix),
441-
Name: "Test Graduated Rate Card " + keyPrefix,
431+
Key: f.Key,
432+
Name: "Test Graduated Rate Card " + f.Key,
442433
Price: price,
443434
BillingCadence: &cadence,
444435
PaymentTerm: &term,
436+
Feature: &apiv3.FeatureReferenceItem{Id: f.Id},
445437
}
446438
}
447439

0 commit comments

Comments
 (0)