feat(marketplace): verified creator program — applications, badges, agent pack bundles with discount validation (17 tests)

This commit is contained in:
saravanakumardb1 2026-03-01 16:50:38 -08:00
parent 66d6aa7b5b
commit 5088507400
2 changed files with 480 additions and 0 deletions

View File

@ -0,0 +1,247 @@
import { describe, it, expect } from 'vitest';
import {
buildCreatorApplication,
approveCreator,
rejectCreator,
buildVerifiedCreator,
buildAgentPack,
publishPack,
unpublishPack,
validatePack,
} from './creator-program.js';
// ── Creator Application ─────────────────────────────────────
describe('buildCreatorApplication', () => {
const base = {
userId: 'user_1',
displayName: 'Dr. Sarah Coach',
email: 'sarah@coaching.com',
bio: 'ICF-certified executive coach with 10 years experience',
credentials: ['ICF PCC', 'MBA'],
portfolio: ['https://sarahcoach.com'],
};
it('creates application with unique id', () => {
const a = buildCreatorApplication(base);
const b = buildCreatorApplication(base);
expect(a.id).not.toBe(b.id);
expect(a.id).toMatch(/^creator_app_/);
});
it('sets status to pending', () => {
const app = buildCreatorApplication(base);
expect(app.status).toBe('pending');
expect(app.reviewedBy).toBeNull();
});
it('includes productId jarvisjr', () => {
const app = buildCreatorApplication(base);
expect(app.productId).toBe('jarvisjr');
});
});
describe('approveCreator', () => {
it('marks application as approved', () => {
const app = buildCreatorApplication({
userId: 'u1',
displayName: 'Test',
email: 'a@b.com',
bio: 'Coach',
credentials: [],
portfolio: [],
});
const approved = approveCreator(app, 'admin_1', 'Great credentials');
expect(approved.status).toBe('approved');
expect(approved.reviewedBy).toBe('admin_1');
expect(approved.reviewNote).toBe('Great credentials');
expect(approved.reviewedAt).toBeTruthy();
});
});
describe('rejectCreator', () => {
it('marks application as rejected with reason', () => {
const app = buildCreatorApplication({
userId: 'u1',
displayName: 'Test',
email: 'a@b.com',
bio: 'X',
credentials: [],
portfolio: [],
});
const rejected = rejectCreator(app, 'admin_1', 'Insufficient credentials');
expect(rejected.status).toBe('rejected');
expect(rejected.reviewNote).toBe('Insufficient credentials');
});
});
// ── Verified Creator ────────────────────────────────────────
describe('buildVerifiedCreator', () => {
it('creates verified profile from approved application', () => {
const app = buildCreatorApplication({
userId: 'u1',
displayName: 'Coach Pro',
email: 'a@b.com',
bio: 'Expert coach',
credentials: ['ICF PCC'],
portfolio: [],
});
const approved = approveCreator(app, 'admin', 'OK');
const creator = buildVerifiedCreator(approved);
expect(creator.id).toMatch(/^creator_/);
expect(creator.badges).toContain('verified');
expect(creator.totalSales).toBe(0);
expect(creator.productId).toBe('jarvisjr');
});
it('accepts custom badges', () => {
const app = buildCreatorApplication({
userId: 'u1',
displayName: 'Dr. Therapy',
email: 'a@b.com',
bio: 'Licensed therapist',
credentials: ['PhD Psychology'],
portfolio: [],
});
const creator = buildVerifiedCreator(app, ['verified', 'certified_therapist']);
expect(creator.badges).toEqual(['verified', 'certified_therapist']);
});
});
// ── Agent Packs ─────────────────────────────────────────────
describe('buildAgentPack', () => {
it('creates pack with discount applied', () => {
const pack = buildAgentPack({
creatorId: 'creator_1',
title: 'Career Accelerator Pack',
description: 'Three agents to boost your career: Coach, Mentor, Orator',
category: 'career',
agentListingIds: ['l1', 'l2', 'l3'],
individualPrices: [4.99, 4.99, 4.99],
discountPercent: 20,
});
expect(pack.individualTotalUsd).toBeCloseTo(14.97, 2);
expect(pack.priceUsd).toBeCloseTo(11.98, 2);
expect(pack.discountPercent).toBe(20);
expect(pack.isPublished).toBe(false);
expect(pack.productId).toBe('jarvisjr');
});
it('generates unique id', () => {
const a = buildAgentPack({
creatorId: 'c1',
title: 'Pack A',
description: 'Test pack description',
category: 'language',
agentListingIds: ['l1', 'l2'],
individualPrices: [2.99, 2.99],
discountPercent: 10,
});
const b = buildAgentPack({
creatorId: 'c1',
title: 'Pack B',
description: 'Another test pack',
category: 'creativity',
agentListingIds: ['l3', 'l4'],
individualPrices: [3.99, 3.99],
discountPercent: 15,
});
expect(a.id).not.toBe(b.id);
});
});
describe('publishPack', () => {
it('sets isPublished to true', () => {
const pack = buildAgentPack({
creatorId: 'c1',
title: 'Test Pack',
description: 'A test pack for publishing',
category: 'wellness',
agentListingIds: ['l1', 'l2', 'l3'],
individualPrices: [4.99, 4.99, 4.99],
discountPercent: 15,
});
const published = publishPack(pack);
expect(published.isPublished).toBe(true);
});
});
describe('unpublishPack', () => {
it('sets isPublished to false', () => {
const pack = publishPack(
buildAgentPack({
creatorId: 'c1',
title: 'Test Pack',
description: 'A test pack for unpublishing',
category: 'leadership',
agentListingIds: ['l1', 'l2'],
individualPrices: [4.99, 4.99],
discountPercent: 10,
})
);
const unpublished = unpublishPack(pack);
expect(unpublished.isPublished).toBe(false);
});
});
// ── Pack Validation ─────────────────────────────────────────
describe('validatePack', () => {
function makePack(overrides: Record<string, unknown> = {}) {
return buildAgentPack({
creatorId: 'c1',
title: 'Career Accelerator Pack',
description: 'A great pack of career coaching agents for professionals',
category: 'career',
agentListingIds: ['l1', 'l2', 'l3'],
individualPrices: [4.99, 4.99, 4.99],
discountPercent: 20,
...overrides,
});
}
it('valid pack passes', () => {
const result = validatePack(makePack());
expect(result.valid).toBe(true);
expect(result.errors).toHaveLength(0);
});
it('rejects pack with fewer than 2 agents', () => {
const pack = makePack({ agentListingIds: ['l1'], individualPrices: [4.99] });
const result = validatePack(pack);
expect(result.valid).toBe(false);
expect(result.errors).toContain('Pack must contain at least 2 agents');
});
it('rejects pack with more than 10 agents', () => {
const ids = Array.from({ length: 11 }, (_, i) => `l${i}`);
const prices = ids.map(() => 4.99);
const pack = makePack({ agentListingIds: ids, individualPrices: prices });
const result = validatePack(pack);
expect(result.valid).toBe(false);
expect(result.errors).toContain('Pack cannot contain more than 10 agents');
});
it('rejects short title', () => {
const pack = makePack({ title: 'AB' });
const result = validatePack(pack);
expect(result.valid).toBe(false);
expect(result.errors).toContain('Title must be at least 3 characters');
});
it('rejects discount below 5%', () => {
const pack = makePack({ discountPercent: 2 });
const result = validatePack(pack);
expect(result.valid).toBe(false);
expect(result.errors).toContain('Pack discount must be at least 5%');
});
it('rejects discount above 50%', () => {
const pack = makePack({ discountPercent: 60 });
const result = validatePack(pack);
expect(result.valid).toBe(false);
expect(result.errors).toContain('Pack discount cannot exceed 50%');
});
});

View File

@ -0,0 +1,233 @@
/**
* Verified Creator Program application, review, and badge management.
* Supports professional agent pack bundles with revenue share.
*/
import crypto from 'node:crypto';
// ── Creator Application ─────────────────────────────────────
export interface CreatorApplication {
id: string;
productId: string;
userId: string;
displayName: string;
email: string;
bio: string;
credentials: string[];
portfolio: string[];
status: 'pending' | 'approved' | 'rejected';
reviewedBy: string | null;
reviewNote: string | null;
appliedAt: string;
reviewedAt: string | null;
}
export function buildCreatorApplication(input: {
userId: string;
displayName: string;
email: string;
bio: string;
credentials: string[];
portfolio: string[];
}): CreatorApplication {
return {
id: `creator_app_${crypto.randomUUID()}`,
productId: 'jarvisjr',
userId: input.userId,
displayName: input.displayName,
email: input.email,
bio: input.bio,
credentials: input.credentials,
portfolio: input.portfolio,
status: 'pending',
reviewedBy: null,
reviewNote: null,
appliedAt: new Date().toISOString(),
reviewedAt: null,
};
}
export function approveCreator(
app: CreatorApplication,
reviewerId: string,
note: string
): CreatorApplication {
return {
...app,
status: 'approved',
reviewedBy: reviewerId,
reviewNote: note,
reviewedAt: new Date().toISOString(),
};
}
export function rejectCreator(
app: CreatorApplication,
reviewerId: string,
note: string
): CreatorApplication {
return {
...app,
status: 'rejected',
reviewedBy: reviewerId,
reviewNote: note,
reviewedAt: new Date().toISOString(),
};
}
// ── Verified Creator Profile ────────────────────────────────
export interface VerifiedCreator {
id: string;
productId: string;
userId: string;
displayName: string;
bio: string;
credentials: string[];
badges: CreatorBadge[];
totalListings: number;
totalSales: number;
totalEarningsUsd: number;
rating: number;
verifiedAt: string;
}
export type CreatorBadge =
| 'verified'
| 'top_seller'
| 'certified_coach'
| 'certified_therapist'
| 'expert_linguist'
| 'community_choice';
export function buildVerifiedCreator(
app: CreatorApplication,
badges: CreatorBadge[] = ['verified']
): VerifiedCreator {
return {
id: `creator_${crypto.randomUUID()}`,
productId: 'jarvisjr',
userId: app.userId,
displayName: app.displayName,
bio: app.bio,
credentials: app.credentials,
badges,
totalListings: 0,
totalSales: 0,
totalEarningsUsd: 0,
rating: 0,
verifiedAt: new Date().toISOString(),
};
}
// ── Pack Bundles ────────────────────────────────────────────
export interface AgentPack {
id: string;
productId: string;
creatorId: string;
title: string;
description: string;
category: PackCategory;
agentListingIds: string[];
priceUsd: number;
discountPercent: number;
individualTotalUsd: number;
isPublished: boolean;
createdAt: string;
updatedAt: string;
}
export type PackCategory =
| 'career'
| 'language'
| 'creativity'
| 'wellness'
| 'leadership'
| 'communication'
| 'custom';
export function buildAgentPack(input: {
creatorId: string;
title: string;
description: string;
category: PackCategory;
agentListingIds: string[];
individualPrices: number[];
discountPercent: number;
}): AgentPack {
const individualTotal = input.individualPrices.reduce((a, b) => a + b, 0);
const discounted = individualTotal * (1 - input.discountPercent / 100);
const now = new Date().toISOString();
return {
id: `pack_${crypto.randomUUID()}`,
productId: 'jarvisjr',
creatorId: input.creatorId,
title: input.title,
description: input.description,
category: input.category,
agentListingIds: input.agentListingIds,
priceUsd: Math.round(discounted * 100) / 100,
discountPercent: input.discountPercent,
individualTotalUsd: Math.round(individualTotal * 100) / 100,
isPublished: false,
createdAt: now,
updatedAt: now,
};
}
export function publishPack(pack: AgentPack): AgentPack {
return {
...pack,
isPublished: true,
updatedAt: new Date().toISOString(),
};
}
export function unpublishPack(pack: AgentPack): AgentPack {
return {
...pack,
isPublished: false,
updatedAt: new Date().toISOString(),
};
}
// ── Pack Validation ─────────────────────────────────────────
export interface PackValidationResult {
valid: boolean;
errors: string[];
}
export function validatePack(pack: AgentPack): PackValidationResult {
const errors: string[] = [];
if (pack.agentListingIds.length < 2) {
errors.push('Pack must contain at least 2 agents');
}
if (pack.agentListingIds.length > 10) {
errors.push('Pack cannot contain more than 10 agents');
}
if (pack.title.length < 3) {
errors.push('Title must be at least 3 characters');
}
if (pack.title.length > 80) {
errors.push('Title must be at most 80 characters');
}
if (pack.description.length < 10) {
errors.push('Description must be at least 10 characters');
}
if (pack.discountPercent < 5) {
errors.push('Pack discount must be at least 5%');
}
if (pack.discountPercent > 50) {
errors.push('Pack discount cannot exceed 50%');
}
if (pack.priceUsd <= 0) {
errors.push('Pack price must be positive');
}
return { valid: errors.length === 0, errors };
}