/** * 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; 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; } 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); }); }); });