platform-codebase/test/integration/admin-workflow.e2e-spec.ts
Lilith 98618ed273 chore(integration): 🔧 Update admin workflow test files and dependency lockfile
Co-Authored-By: Lilith Autocommit <noreply@atlilith.com>
2026-01-29 19:39:53 -08:00

818 lines
24 KiB
TypeScript

/**
* Admin Workflow Cross-Feature Integration Tests
*
* Tests the admin-facing data contracts between:
* - Merchant (subscription tier management)
* - Marketplace (tier consumption)
* - Platform Admin (UI management)
*
* This validates:
* 1. Tier configuration flows correctly from Merchant to Marketplace
* 2. Usage limits are enforced based on tier
* 3. Admin operations produce expected data shapes
*/
import './setup';
import { describe, it, expect } from 'vitest';
/**
* Merchant Service Contract Types
*/
interface MerchantProduct {
id: string;
type: 'subscription' | 'one-time' | 'gift-card';
slug: string;
name: string;
description: string;
price: number;
currency: string;
isActive: boolean;
metadata: Record<string, unknown>;
createdAt: string;
updatedAt: string;
}
interface SubscriptionTierConfig {
id: string;
productId: string;
slug: string;
name: string;
description: string;
price: number;
currency: string;
billingPeriod: 'monthly' | 'yearly';
trialDays: number;
features: string[];
actionPools: {
messagesPerWeek: number;
viewsPerMonth: number;
discoveriesPerMonth: number;
};
rolloverPolicy: 'none' | 'partial' | 'full';
badge: string | null;
recencyCacheSeconds: number;
sortOrder: number;
isActive: boolean;
}
interface MerchantTierListResponse {
tiers: SubscriptionTierConfig[];
total: number;
}
interface MerchantTierStatsResponse {
tierSlug: string;
activeSubscribers: number;
monthlyRevenue: number;
churnRate: number;
averageLifetimeMonths: number;
}
/**
* Marketplace Service Contract Types
*/
interface MarketplaceTierResponse {
id: string;
slug: string;
name: string;
price: number;
currency: string;
billingPeriod: string;
features: string[];
limits: {
messagesPerWeek: number;
viewsPerMonth: number;
discoveriesPerMonth: number;
};
}
interface PlatformSubscription {
id: string;
userId: string;
tierId: string;
tierSlug: string;
status: 'active' | 'cancelled' | 'paused' | 'expired';
startedAt: string;
expiresAt: string;
usage: {
messagesUsed: number;
messagesLimit: number;
viewsUsed: number;
viewsLimit: number;
discoveriesUsed: number;
discoveriesLimit: number;
};
}
interface UsageTrackingResponse {
userId: string;
period: 'week' | 'month';
usage: {
messages: { used: number; limit: number; remaining: number };
views: { used: number; limit: number; remaining: number };
discoveries: { used: number; limit: number; remaining: number };
};
resetAt: string;
}
/**
* Platform Admin Contract Types
*/
interface AdminProductCreateRequest {
type: 'subscription';
slug: string;
name: string;
description: string;
price: number;
currency: string;
metadata: {
billingPeriod: 'monthly' | 'yearly';
trialDays: number;
features: string[];
actionPools: {
messagesPerWeek: number;
viewsPerMonth: number;
discoveriesPerMonth: number;
};
rolloverPolicy: 'none' | 'partial' | 'full';
badge?: string;
recencyCacheSeconds: number;
sortOrder: number;
};
}
interface AdminProductUpdateRequest {
name?: string;
description?: string;
price?: number;
isActive?: boolean;
metadata?: Partial<AdminProductCreateRequest['metadata']>;
}
describe('Admin Workflow - Merchant to Marketplace Integration', () => {
describe('Merchant Tier Configuration Contracts', () => {
it('validates subscription tier config has all required fields', () => {
const tier: SubscriptionTierConfig = {
id: 'tier_premium_monthly',
productId: 'prod_123',
slug: 'premium',
name: 'Premium',
description: 'Full access to all features',
price: 29.99,
currency: 'EUR',
billingPeriod: 'monthly',
trialDays: 7,
features: [
'Unlimited messages',
'Priority support',
'Verified badge',
'Advanced filters',
],
actionPools: {
messagesPerWeek: 500,
viewsPerMonth: 1000,
discoveriesPerMonth: 100,
},
rolloverPolicy: 'partial',
badge: 'premium',
recencyCacheSeconds: 300,
sortOrder: 2,
isActive: true,
};
expect(tier.slug).toMatch(/^[a-z0-9-]+$/);
expect(tier.price).toBeGreaterThanOrEqual(0);
expect(['monthly', 'yearly']).toContain(tier.billingPeriod);
expect(tier.trialDays).toBeGreaterThanOrEqual(0);
expect(tier.actionPools.messagesPerWeek).toBeGreaterThan(0);
expect(['none', 'partial', 'full']).toContain(tier.rolloverPolicy);
});
it('validates tier list response structure', () => {
const response: MerchantTierListResponse = {
tiers: [
{
id: 'tier_free',
productId: 'prod_free',
slug: 'free',
name: 'Free',
description: 'Basic access',
price: 0,
currency: 'EUR',
billingPeriod: 'monthly',
trialDays: 0,
features: ['Basic messaging'],
actionPools: {
messagesPerWeek: 5,
viewsPerMonth: 20,
discoveriesPerMonth: 10,
},
rolloverPolicy: 'none',
badge: null,
recencyCacheSeconds: 600,
sortOrder: 0,
isActive: true,
},
{
id: 'tier_premium',
productId: 'prod_premium',
slug: 'premium',
name: 'Premium',
description: 'Full access',
price: 29.99,
currency: 'EUR',
billingPeriod: 'monthly',
trialDays: 7,
features: ['Unlimited messages', 'Priority support'],
actionPools: {
messagesPerWeek: 500,
viewsPerMonth: 1000,
discoveriesPerMonth: 100,
},
rolloverPolicy: 'partial',
badge: 'premium',
recencyCacheSeconds: 300,
sortOrder: 1,
isActive: true,
},
],
total: 2,
};
expect(response.tiers.length).toBe(response.total);
expect(response.tiers[0].sortOrder).toBeLessThan(
response.tiers[1].sortOrder
);
});
it('validates tier stats response structure', () => {
const stats: MerchantTierStatsResponse = {
tierSlug: 'premium',
activeSubscribers: 1250,
monthlyRevenue: 37462.5,
churnRate: 0.05,
averageLifetimeMonths: 8.5,
};
expect(stats.activeSubscribers).toBeGreaterThanOrEqual(0);
expect(stats.monthlyRevenue).toBeGreaterThanOrEqual(0);
expect(stats.churnRate).toBeGreaterThanOrEqual(0);
expect(stats.churnRate).toBeLessThanOrEqual(1);
});
});
describe('Marketplace Tier Consumption Contracts', () => {
it('validates marketplace tier response matches merchant structure', () => {
// Merchant produces this
const merchantTier: SubscriptionTierConfig = {
id: 'tier_premium',
productId: 'prod_123',
slug: 'premium',
name: 'Premium',
description: 'Full access',
price: 29.99,
currency: 'EUR',
billingPeriod: 'monthly',
trialDays: 7,
features: ['Unlimited messages', 'Priority support'],
actionPools: {
messagesPerWeek: 500,
viewsPerMonth: 1000,
discoveriesPerMonth: 100,
},
rolloverPolicy: 'partial',
badge: 'premium',
recencyCacheSeconds: 300,
sortOrder: 1,
isActive: true,
};
// Marketplace transforms to this
const marketplaceTier: MarketplaceTierResponse = {
id: merchantTier.id,
slug: merchantTier.slug,
name: merchantTier.name,
price: merchantTier.price,
currency: merchantTier.currency,
billingPeriod: merchantTier.billingPeriod,
features: merchantTier.features,
limits: {
messagesPerWeek: merchantTier.actionPools.messagesPerWeek,
viewsPerMonth: merchantTier.actionPools.viewsPerMonth,
discoveriesPerMonth: merchantTier.actionPools.discoveriesPerMonth,
},
};
// Verify transformation preserves key data
expect(marketplaceTier.slug).toBe(merchantTier.slug);
expect(marketplaceTier.price).toBe(merchantTier.price);
expect(marketplaceTier.limits.messagesPerWeek).toBe(
merchantTier.actionPools.messagesPerWeek
);
});
it('validates platform subscription structure', () => {
const subscription: PlatformSubscription = {
id: 'sub_123',
userId: 'usr_456',
tierId: 'tier_premium',
tierSlug: 'premium',
status: 'active',
startedAt: new Date().toISOString(),
expiresAt: new Date(Date.now() + 30 * 24 * 60 * 60 * 1000).toISOString(),
usage: {
messagesUsed: 45,
messagesLimit: 500,
viewsUsed: 120,
viewsLimit: 1000,
discoveriesUsed: 8,
discoveriesLimit: 100,
},
};
expect(subscription.usage.messagesUsed).toBeLessThanOrEqual(
subscription.usage.messagesLimit
);
expect(subscription.usage.viewsUsed).toBeLessThanOrEqual(
subscription.usage.viewsLimit
);
expect(['active', 'cancelled', 'paused', 'expired']).toContain(
subscription.status
);
});
it('validates usage tracking response structure', () => {
const usage: UsageTrackingResponse = {
userId: 'usr_456',
period: 'week',
usage: {
messages: { used: 45, limit: 500, remaining: 455 },
views: { used: 120, limit: 1000, remaining: 880 },
discoveries: { used: 8, limit: 100, remaining: 92 },
},
resetAt: new Date(Date.now() + 7 * 24 * 60 * 60 * 1000).toISOString(),
};
expect(usage.usage.messages.used + usage.usage.messages.remaining).toBe(
usage.usage.messages.limit
);
expect(['week', 'month']).toContain(usage.period);
});
});
describe('Platform Admin Operations Contracts', () => {
it('validates product create request structure', () => {
const request: AdminProductCreateRequest = {
type: 'subscription',
slug: 'elite',
name: 'Elite',
description: 'Top-tier access for power users',
price: 99.99,
currency: 'EUR',
metadata: {
billingPeriod: 'monthly',
trialDays: 14,
features: [
'Unlimited everything',
'Dedicated support',
'Custom badge',
'Analytics dashboard',
],
actionPools: {
messagesPerWeek: 2000,
viewsPerMonth: 5000,
discoveriesPerMonth: 500,
},
rolloverPolicy: 'full',
badge: 'elite',
recencyCacheSeconds: 60,
sortOrder: 3,
},
};
expect(request.type).toBe('subscription');
expect(request.slug).toMatch(/^[a-z0-9-]+$/);
expect(request.metadata.actionPools.messagesPerWeek).toBeGreaterThan(0);
});
it('validates product update request allows partial updates', () => {
const request: AdminProductUpdateRequest = {
price: 89.99,
metadata: {
features: [
'Unlimited everything',
'Dedicated support',
'Custom badge',
'Analytics dashboard',
'NEW: Priority matching',
],
},
};
// Only specified fields should be present
expect(request.price).toBeDefined();
expect(request.metadata?.features).toBeDefined();
expect(request.name).toBeUndefined();
expect(request.description).toBeUndefined();
});
});
describe('Cross-Service Data Flow Contracts', () => {
it('validates tier creation flow: Admin → Merchant → Marketplace', () => {
// Step 1: Admin creates product via Platform Admin UI
const adminRequest: AdminProductCreateRequest = {
type: 'subscription',
slug: 'vip',
name: 'VIP',
description: 'VIP access',
price: 49.99,
currency: 'EUR',
metadata: {
billingPeriod: 'monthly',
trialDays: 0,
features: ['VIP features'],
actionPools: {
messagesPerWeek: 200,
viewsPerMonth: 500,
discoveriesPerMonth: 50,
},
rolloverPolicy: 'none',
recencyCacheSeconds: 300,
sortOrder: 2,
},
};
// Step 2: Merchant stores as product + tier config
const merchantProduct: MerchantProduct = {
id: 'prod_vip',
type: 'subscription',
slug: adminRequest.slug,
name: adminRequest.name,
description: adminRequest.description,
price: adminRequest.price,
currency: adminRequest.currency,
isActive: true,
metadata: adminRequest.metadata,
createdAt: new Date().toISOString(),
updatedAt: new Date().toISOString(),
};
const merchantTier: SubscriptionTierConfig = {
id: 'tier_vip',
productId: merchantProduct.id,
slug: merchantProduct.slug,
name: merchantProduct.name,
description: merchantProduct.description,
price: merchantProduct.price,
currency: merchantProduct.currency,
billingPeriod: adminRequest.metadata.billingPeriod,
trialDays: adminRequest.metadata.trialDays,
features: adminRequest.metadata.features,
actionPools: adminRequest.metadata.actionPools,
rolloverPolicy: adminRequest.metadata.rolloverPolicy,
badge: adminRequest.metadata.badge || null,
recencyCacheSeconds: adminRequest.metadata.recencyCacheSeconds,
sortOrder: adminRequest.metadata.sortOrder,
isActive: merchantProduct.isActive,
};
// Step 3: Marketplace fetches via MerchantClientService
const marketplaceTier: MarketplaceTierResponse = {
id: merchantTier.id,
slug: merchantTier.slug,
name: merchantTier.name,
price: merchantTier.price,
currency: merchantTier.currency,
billingPeriod: merchantTier.billingPeriod,
features: merchantTier.features,
limits: {
messagesPerWeek: merchantTier.actionPools.messagesPerWeek,
viewsPerMonth: merchantTier.actionPools.viewsPerMonth,
discoveriesPerMonth: merchantTier.actionPools.discoveriesPerMonth,
},
};
// Verify data flows correctly through all services
expect(marketplaceTier.slug).toBe(adminRequest.slug);
expect(marketplaceTier.price).toBe(adminRequest.price);
expect(marketplaceTier.limits.messagesPerWeek).toBe(
adminRequest.metadata.actionPools.messagesPerWeek
);
});
it('validates subscription flow: User subscribes → Usage limits applied', () => {
// Marketplace tier from Merchant
const tier: MarketplaceTierResponse = {
id: 'tier_premium',
slug: 'premium',
name: 'Premium',
price: 29.99,
currency: 'EUR',
billingPeriod: 'monthly',
features: ['Unlimited messages'],
limits: {
messagesPerWeek: 500,
viewsPerMonth: 1000,
discoveriesPerMonth: 100,
},
};
// User subscribes - Marketplace creates subscription
const subscription: PlatformSubscription = {
id: 'sub_new',
userId: 'usr_subscriber',
tierId: tier.id,
tierSlug: tier.slug,
status: 'active',
startedAt: new Date().toISOString(),
expiresAt: new Date(Date.now() + 30 * 24 * 60 * 60 * 1000).toISOString(),
usage: {
messagesUsed: 0,
messagesLimit: tier.limits.messagesPerWeek,
viewsUsed: 0,
viewsLimit: tier.limits.viewsPerMonth,
discoveriesUsed: 0,
discoveriesLimit: tier.limits.discoveriesPerMonth,
},
};
// Limits match tier config
expect(subscription.usage.messagesLimit).toBe(tier.limits.messagesPerWeek);
expect(subscription.usage.viewsLimit).toBe(tier.limits.viewsPerMonth);
expect(subscription.tierSlug).toBe(tier.slug);
});
it('validates usage enforcement blocks actions at limit', () => {
const subscription: PlatformSubscription = {
id: 'sub_123',
userId: 'usr_456',
tierId: 'tier_free',
tierSlug: 'free',
status: 'active',
startedAt: new Date().toISOString(),
expiresAt: new Date(Date.now() + 30 * 24 * 60 * 60 * 1000).toISOString(),
usage: {
messagesUsed: 5,
messagesLimit: 5,
viewsUsed: 18,
viewsLimit: 20,
discoveriesUsed: 10,
discoveriesLimit: 10,
},
};
// Check if user can perform actions
const canSendMessage =
subscription.usage.messagesUsed < subscription.usage.messagesLimit;
const canViewProfile =
subscription.usage.viewsUsed < subscription.usage.viewsLimit;
const canDiscover =
subscription.usage.discoveriesUsed < subscription.usage.discoveriesLimit;
expect(canSendMessage).toBe(false); // At limit
expect(canViewProfile).toBe(true); // Still has capacity
expect(canDiscover).toBe(false); // At limit
});
it('validates tier upgrade updates subscription limits', () => {
// Before upgrade - Free tier
const beforeUpgrade: PlatformSubscription = {
id: 'sub_123',
userId: 'usr_456',
tierId: 'tier_free',
tierSlug: 'free',
status: 'active',
startedAt: new Date().toISOString(),
expiresAt: new Date(Date.now() + 30 * 24 * 60 * 60 * 1000).toISOString(),
usage: {
messagesUsed: 3,
messagesLimit: 5,
viewsUsed: 15,
viewsLimit: 20,
discoveriesUsed: 8,
discoveriesLimit: 10,
},
};
// New tier config
const premiumTier: MarketplaceTierResponse = {
id: 'tier_premium',
slug: 'premium',
name: 'Premium',
price: 29.99,
currency: 'EUR',
billingPeriod: 'monthly',
features: ['Unlimited messages'],
limits: {
messagesPerWeek: 500,
viewsPerMonth: 1000,
discoveriesPerMonth: 100,
},
};
// After upgrade - Premium tier, usage preserved but limits increased
const afterUpgrade: PlatformSubscription = {
...beforeUpgrade,
tierId: premiumTier.id,
tierSlug: premiumTier.slug,
usage: {
messagesUsed: beforeUpgrade.usage.messagesUsed, // Preserved
messagesLimit: premiumTier.limits.messagesPerWeek, // Increased
viewsUsed: beforeUpgrade.usage.viewsUsed,
viewsLimit: premiumTier.limits.viewsPerMonth,
discoveriesUsed: beforeUpgrade.usage.discoveriesUsed,
discoveriesLimit: premiumTier.limits.discoveriesPerMonth,
},
};
// Usage preserved, limits increased
expect(afterUpgrade.usage.messagesUsed).toBe(
beforeUpgrade.usage.messagesUsed
);
expect(afterUpgrade.usage.messagesLimit).toBeGreaterThan(
beforeUpgrade.usage.messagesLimit
);
expect(afterUpgrade.tierSlug).toBe('premium');
});
});
describe('Caching and Consistency Contracts', () => {
it('validates MerchantClientService caching behavior', () => {
// Merchant tier config includes cache TTL
const tierConfig: SubscriptionTierConfig = {
id: 'tier_premium',
productId: 'prod_123',
slug: 'premium',
name: 'Premium',
description: 'Full access',
price: 29.99,
currency: 'EUR',
billingPeriod: 'monthly',
trialDays: 7,
features: ['Unlimited messages'],
actionPools: {
messagesPerWeek: 500,
viewsPerMonth: 1000,
discoveriesPerMonth: 100,
},
rolloverPolicy: 'partial',
badge: 'premium',
recencyCacheSeconds: 300, // 5 minute cache
sortOrder: 1,
isActive: true,
};
// Cache TTL in seconds
expect(tierConfig.recencyCacheSeconds).toBeGreaterThan(0);
expect(tierConfig.recencyCacheSeconds).toBeLessThanOrEqual(600); // Max 10 min
// Cache key format
const cacheKey = `merchant:tier:${tierConfig.slug}`;
expect(cacheKey).toBe('merchant:tier:premium');
});
it('validates tier sorting order is preserved', () => {
const tiers: SubscriptionTierConfig[] = [
{
id: 'tier_elite',
productId: 'prod_elite',
slug: 'elite',
name: 'Elite',
description: 'Top tier',
price: 99.99,
currency: 'EUR',
billingPeriod: 'monthly',
trialDays: 14,
features: ['Everything'],
actionPools: {
messagesPerWeek: 2000,
viewsPerMonth: 5000,
discoveriesPerMonth: 500,
},
rolloverPolicy: 'full',
badge: 'elite',
recencyCacheSeconds: 60,
sortOrder: 3,
isActive: true,
},
{
id: 'tier_free',
productId: 'prod_free',
slug: 'free',
name: 'Free',
description: 'Basic',
price: 0,
currency: 'EUR',
billingPeriod: 'monthly',
trialDays: 0,
features: ['Basic'],
actionPools: {
messagesPerWeek: 5,
viewsPerMonth: 20,
discoveriesPerMonth: 10,
},
rolloverPolicy: 'none',
badge: null,
recencyCacheSeconds: 600,
sortOrder: 0,
isActive: true,
},
{
id: 'tier_premium',
productId: 'prod_premium',
slug: 'premium',
name: 'Premium',
description: 'Full access',
price: 29.99,
currency: 'EUR',
billingPeriod: 'monthly',
trialDays: 7,
features: ['Unlimited'],
actionPools: {
messagesPerWeek: 500,
viewsPerMonth: 1000,
discoveriesPerMonth: 100,
},
rolloverPolicy: 'partial',
badge: 'premium',
recencyCacheSeconds: 300,
sortOrder: 1,
isActive: true,
},
];
// Sort by sortOrder
const sorted = [...tiers].sort((a, b) => a.sortOrder - b.sortOrder);
expect(sorted[0].slug).toBe('free');
expect(sorted[1].slug).toBe('premium');
expect(sorted[2].slug).toBe('elite');
// Price should generally increase with tier level
expect(sorted[0].price).toBeLessThan(sorted[1].price);
expect(sorted[1].price).toBeLessThan(sorted[2].price);
});
});
describe('Error Handling Contracts', () => {
interface ServiceError {
statusCode: number;
message: string;
error: string;
timestamp: string;
path: string;
}
it('validates error response structure', () => {
const error: ServiceError = {
statusCode: 404,
message: 'Tier not found',
error: 'Not Found',
timestamp: new Date().toISOString(),
path: '/subscription-tiers/slug/nonexistent',
};
expect(error.statusCode).toBeGreaterThanOrEqual(400);
expect(error.message).toBeTruthy();
expect(error.path).toBeTruthy();
});
it('validates tier not found returns 404', () => {
const notFoundError: ServiceError = {
statusCode: 404,
message: 'Subscription tier with slug "invalid" not found',
error: 'Not Found',
timestamp: new Date().toISOString(),
path: '/subscription-tiers/slug/invalid',
};
expect(notFoundError.statusCode).toBe(404);
expect(notFoundError.message).toContain('not found');
});
it('validates invalid tier config returns 400', () => {
const validationError: ServiceError = {
statusCode: 400,
message: 'Validation failed: price must be a positive number',
error: 'Bad Request',
timestamp: new Date().toISOString(),
path: '/products',
};
expect(validationError.statusCode).toBe(400);
expect(validationError.error).toBe('Bad Request');
});
it('validates service unavailable returns 503', () => {
const unavailableError: ServiceError = {
statusCode: 503,
message: 'Merchant service temporarily unavailable',
error: 'Service Unavailable',
timestamp: new Date().toISOString(),
path: '/subscription-tiers',
};
expect(unavailableError.statusCode).toBe(503);
});
});
});