Files
cfw-autumn/server/tests/testRunner/MIGRATION_GUIDE.md
2025-10-24 10:11:16 +01:00

293 lines
7.5 KiB
Markdown

# Test Migration Guide: Global State → Parallel-Ready Tests
## Status
- ✅ Phase 1: Migration guide created
- ✅ Phase 2: Conversion utilities completed
- ✅ Phase 3: Validation process documented
- ✅ Phase 4: Initial test migrations validated (basic1, basic2)
**See [VALIDATION_RESULTS.md](./VALIDATION_RESULTS.md) for detailed validation analysis.**
## Validation Process
**CRITICAL:** Before replacing any test file, you MUST validate the migration preserves test logic.
### Step-by-Step Validation
1. **Create New File** - Don't modify original
```bash
# Create basic1.new.test.ts (not basic1.test.ts)
```
2. **Run Original Test** - Uses global state
```bash
cd server
bun test tests/attach/basic/basic1.test.ts
```
- Note all assertions and expected values
- Save output for comparison
3. **Run Migrated Test** - Uses inline products
```bash
cd server
bun parallel-tests
# Or configure config.ts to point to basic1.new.test.ts
```
4. **Compare Test Logic** - **NOT** output values
- ✅ Same test structure (beforeAll, test blocks)
- ✅ Same assertions (expect calls)
- ✅ Same logic flow
- ❌ Don't compare feature IDs (metered1 → Messages is OK)
- ❌ Don't compare product names (different orgs)
5. **Critical Checks**
- Both tests pass ✅
- Same number of assertions
- Same expected behavior (e.g., "balance should be 5")
- No logic lost or added
6. **Only After Validation** - Replace original
```bash
mv basic1.new.test.ts basic1.test.ts
```
### Example: basic1.test.ts Migration
**Original** (uses global state):
```typescript
test("should have correct entitlements", async () => {
const expectedEntitlement = products.free.entitlements.metered1;
const entitled = await AutumnCli.entitled(customerId, features.metered1.id);
const balance = entitled.balances.find(b => b.feature_id === features.metered1.id);
expect(entitled.allowed).toBe(true);
expect(balance).toBeDefined();
expect(balance.balance).toBe(expectedEntitlement.allowance); // 5
expect(balance.unlimited).toBeUndefined();
});
```
**Migrated** (uses inline products):
```typescript
test("should have correct entitlements", async () => {
// Expected: 5 allowance for Messages feature (same as metered1)
const entitled = await AutumnCli.entitled(customerId, TestFeature.Messages);
const balance = entitled.balances.find(b => b.feature_id === TestFeature.Messages);
expect(entitled.allowed).toBe(true);
expect(balance).toBeDefined();
expect(balance.balance).toBe(5); // Same expected value
expect(balance.unlimited).toBeUndefined();
});
```
**Key Differences (ALLOWED)**:
- Feature ID: `features.metered1.id` → `TestFeature.Messages`
- Source: `products.free.entitlements.metered1` → inline `freeProd` definition
- Expected value: Hardcoded `5` instead of `expectedEntitlement.allowance`
**What Must Stay Same**:
- Number of expects: 4
- Expected values: balance = 5, allowed = true, unlimited = undefined
- Test logic: Check balance, verify allowed, ensure not unlimited
## Quick Start
### Is Your Test Already Migrated?
**✅ Already Done** - Your test uses:
- Inline product definitions (`constructProduct`)
- V1.2+ API (`AutumnInt` with `LegacyVersion.v1_2` or higher)
- Modern assertions (`expectProductAttached`, `expectFeaturesCorrect`)
**❌ Needs Migration** - Your test uses:
- `products.*` from `tests/global.ts`
- V0.1 API (`AutumnCli.getCustomer()`)
- Legacy assertions (`compareMainProduct`)
## Migration Steps
### 1. Define Products Inline
**Before:**
```typescript
import { products } from "tests/global.js";
// Uses products.pro
```
**After:**
```typescript
import { TestFeature } from "tests/setup/v2Features.js";
import { constructProduct } from "@/utils/scriptUtils/createTestProducts.js";
import { constructFeatureItem } from "@/utils/scriptUtils/constructItem.js";
const pro = constructProduct({
type: "pro",
items: [
constructFeatureItem({
featureId: TestFeature.Messages,
includedUsage: 10,
}),
],
});
```
### 2. Initialize Products in beforeAll
```typescript
beforeAll(async () => {
await initProductsV0({
ctx,
products: [pro], // Pass inline products
prefix: testCase,
});
});
```
### 3. Update Assertions
#### For V0.1 API (AutumnCli):
**Before:**
```typescript
import { compareMainProduct } from "tests/utils/compare.js";
const res = await AutumnCli.getCustomer(customerId);
compareMainProduct({ sent: products.pro, cusRes: res });
```
**After:**
```typescript
import { expectCustomerV0Correct } from "tests/utils/expectUtils/expectCustomerV0Correct.js";
const res = await AutumnCli.getCustomer(customerId);
await expectCustomerV0Correct({ sent: pro, cusRes: res });
```
#### For V1.2+ API (AutumnInt):
**Already correct:**
```typescript
const customer = await autumn.customers.get(customerId);
expectProductAttached({ customer, product: pro });
expectFeaturesCorrect({ customer, product: pro });
```
## Product Mapping Reference
### Free Product
```typescript
// global.ts: products.free
const free = constructProduct({
type: "free",
items: [
constructFeatureItem({
featureId: TestFeature.Messages,
includedUsage: 5,
interval: ProductItemInterval.Month,
}),
],
});
```
### Pro Product
```typescript
// global.ts: products.pro
const pro = constructProduct({
type: "pro",
items: [
constructFeatureItem({
featureId: TestFeature.Messages,
includedUsage: 10,
interval: ProductItemInterval.Month,
}),
],
});
```
### Pro with Overage
```typescript
// global.ts: products.proWithOverage
const proWithOverage = constructProduct({
type: "pro",
items: [
constructFeatureItem({
featureId: TestFeature.Messages,
includedUsage: 10,
}),
constructArrearItem({
featureId: TestFeature.Messages,
}),
],
});
```
## Common Patterns
### Multiple Features
```typescript
const product = constructProduct({
type: "pro",
items: [
constructFeatureItem({
featureId: TestFeature.Messages,
includedUsage: 100,
}),
constructFeatureItem({
featureId: TestFeature.Dashboard,
isBoolean: true,
}),
],
});
```
### Prepaid/Arrear Pricing
```typescript
const product = constructProduct({
type: "pro",
items: [
constructPrepaidItem({
featureId: TestFeature.Messages,
price: 9,
billingUnits: 100,
}),
// OR
constructArrearItem({
featureId: TestFeature.Words,
price: 0.1,
billingUnits: 1000,
}),
],
});
```
## Files to Migrate
### ✅ Completed
- `tests/attach/basic/basic1.test.ts` - Migrated to basic1.new.test.ts
- `tests/attach/basic/basic2.test.ts` - Migrated to basic2.new.test.ts
### Priority 1 (Simple)
- `tests/attach/basic/basic3.test.ts` - Uses products.premium
### Priority 2 (Medium)
- `tests/attach/upgrade/*.test.ts`
- `tests/attach/downgrade/*.test.ts`
### Priority 3 (Complex)
- `tests/attach/entities/*.test.ts`
- `tests/core/cancel/*.test.ts`
## Utilities Reference
- `constructProduct()` - Create product with items
- `constructFeatureItem()` - Create feature entitlement
- `constructPrepaidItem()` - Create prepaid feature price
- `constructArrearItem()` - Create pay-per-use (single_use) [eg. credits, messages, tokens] feature price
- `constructArrearProratedItem()` - Create pay-per-use (continuous_use) [eg. seats, users, admins] feature price
- `constructFixedPrice()` - Create fixed price
- `expectCustomerV0Correct()` - Compare V2 product with V0.1 customer response
- `expectProductAttached()` - V1.2+ API product check
- `expectFeaturesCorrect()` - V1.2+ API feature balance check