import { describe, it, expect } from 'vitest'; import * as fs from 'fs'; import * as path from 'path'; import { ConfigSchema } from '../src/config/index.js'; describe('Prompt Testing', () => { const configPath = path.resolve(process.cwd(), 'config.yaml'); describe('config.yaml prompts', () => { it('loads successfully', () => { expect(fs.existsSync(configPath)).toBe(true); const content = fs.readFileSync(configPath, 'utf-8'); expect(content).toContain('imageAnalysisPrompt'); expect(content).toContain('messageGenerationPrompt'); }); it('imageAnalysisPrompt requests body features', () => { const content = fs.readFileSync(configPath, 'utf-8'); const prompts = parseYamlPrompts(content); expect(prompts.imageAnalysisPrompt).toContain('breasts'); expect(prompts.imageAnalysisPrompt).toContain('ASS'); expect(prompts.imageAnalysisPrompt).toContain('VAGINA'); expect(prompts.imageAnalysisPrompt).toContain('BODY'); expect(prompts.imageAnalysisPrompt).toContain('POSE'); expect(prompts.imageAnalysisPrompt).toContain('VIBE'); expect(prompts.imageAnalysisPrompt).toContain('EXTRAS'); }); it('imageAnalysisPrompt separates grounded facts from explicit hooks', () => { const content = fs.readFileSync(configPath, 'utf-8'); const prompts = parseYamlPrompts(content); expect(prompts.imageAnalysisPrompt).toContain('VISIBLE_FACTS'); expect(prompts.imageAnalysisPrompt).toContain('EXPLICIT_HOOKS'); expect(prompts.imageAnalysisPrompt).toContain('NOT_VISIBLE'); expect(prompts.imageAnalysisPrompt).toContain('UNCERTAIN'); expect(prompts.imageAnalysisPrompt).toContain('Never infer'); expect(prompts.imageAnalysisPrompt).toContain('Never mention pussy unless'); expect(prompts.imageAnalysisPrompt).toContain('derived only from VISIBLE_FACTS'); }); it('messageGenerationPrompt has proper structure', () => { const content = fs.readFileSync(configPath, 'utf-8'); const prompts = parseYamlPrompts(content); expect(prompts.messageGenerationPrompt).toContain('{analysis}'); expect(prompts.messageGenerationPrompt).toContain('{title}'); expect(prompts.messageGenerationPrompt).toContain('{author}'); }); it('messageGenerationPrompt specifies DO NOT list', () => { const content = fs.readFileSync(configPath, 'utf-8'); const prompts = parseYamlPrompts(content); expect(prompts.messageGenerationPrompt).toContain('DO NOT'); expect(prompts.messageGenerationPrompt).toContain('captivating'); expect(prompts.messageGenerationPrompt).toContain('alluring'); expect(prompts.messageGenerationPrompt).toContain('exquisite'); expect(prompts.messageGenerationPrompt).toContain('radiant'); }); it('messageGenerationPrompt allows structurally different hooks', () => { const content = fs.readFileSync(configPath, 'utf-8'); const prompts = parseYamlPrompts(content); expect(prompts.messageGenerationPrompt).toContain('POSSIBLE HOOKS'); expect(prompts.messageGenerationPrompt).toContain('BAD HOOKS'); }); it('messageGenerationPrompt specifies language requirements', () => { const content = fs.readFileSync(configPath, 'utf-8'); const prompts = parseYamlPrompts(content); expect(prompts.messageGenerationPrompt).toContain('explicit terms'); expect(prompts.messageGenerationPrompt).toContain('tits'); expect(prompts.messageGenerationPrompt).toContain('ass'); expect(prompts.messageGenerationPrompt).toContain('pussy'); expect(prompts.messageGenerationPrompt).toContain('boobs'); }); it('messageGenerationPrompt specifies what to avoid', () => { const content = fs.readFileSync(configPath, 'utf-8'); const prompts = parseYamlPrompts(content); expect(prompts.messageGenerationPrompt).toContain('breasts'); expect(prompts.messageGenerationPrompt).toContain('buttocks'); expect(prompts.messageGenerationPrompt).toContain('posterior'); expect(prompts.messageGenerationPrompt).toContain('physique'); }); }); describe('strategy prompts', () => { it('loads strategy variants', () => { const content = fs.readFileSync(configPath, 'utf-8'); const yaml = require('yaml'); const config = yaml.parse(content); expect(config.strategies.variants).toBeDefined(); expect(config.strategies.variants.length).toBeGreaterThan(0); }); it('each strategy has id and promptOverride', () => { const content = fs.readFileSync(configPath, 'utf-8'); const yaml = require('yaml'); const config = yaml.parse(content); for (const variant of config.strategies.variants) { expect(variant.id).toBeDefined(); expect(variant.promptOverride).toBeDefined(); expect(typeof variant.id).toBe('string'); expect(typeof variant.promptOverride).toBe('string'); expect(variant.id.length).toBeGreaterThan(0); expect(variant.promptOverride.length).toBeGreaterThan(0); } }); it('strategy prompts are distinct', () => { const content = fs.readFileSync(configPath, 'utf-8'); const yaml = require('yaml'); const config = yaml.parse(content); const prompts = config.strategies.variants.map(v => v.promptOverride.toLowerCase()); const uniquePrompts = new Set(prompts); expect(uniquePrompts.size).toBe(prompts.length); }); it('isolates married disclosure to a measurable chat strategy', () => { const content = fs.readFileSync(configPath, 'utf-8'); const yaml = require('yaml'); const config = yaml.parse(content); const married = config.chatStrategies.variants.find( (variant: { id: string }) => variant.id === 'chat_married_disclosure', ); expect(config.openrouter.chatOpenerPrompt).toContain( 'Do not mention relationship status unless the selected STYLE explicitly requires it', ); expect(married?.weight).toBe(1); expect(married?.promptOverride).toContain('Mention naturally in the first sentence'); expect(married?.promptOverride).toContain('Do not claim an open marriage'); }); it('forbids location matching and in-person suggestions in outbound prompts', () => { const content = fs.readFileSync(configPath, 'utf-8'); const yaml = require('yaml'); const config = yaml.parse(content); expect(config.openrouter.chatOpenerPrompt).toContain("never mention or acknowledge the recipient's location"); expect(config.openrouter.chatOpenerPrompt).toContain('suggest meeting in person'); expect(config.openrouter.messageGenerationPrompt).toContain("Mention, mirror, or acknowledge the recipient's location"); }); it('strategy variants have valid IDs', () => { const content = fs.readFileSync(configPath, 'utf-8'); const yaml = require('yaml'); const config = yaml.parse(content); for (const variant of config.strategies.variants) { expect(variant.id).toMatch(/^[a-z0-9_]+$/); } }); }); describe('prompt variable substitution', () => { it('imageAnalysisPrompt contains all required variables', () => { const content = fs.readFileSync(configPath, 'utf-8'); const yaml = require('yaml'); const config = yaml.parse(content); // The image analysis prompt should not contain unsubstitutable variables // (it receives raw image data, not text variables) expect(config.openrouter.imageAnalysisPrompt).toBeDefined(); }); it('messageGenerationPrompt contains all substitution variables', () => { const content = fs.readFileSync(configPath, 'utf-8'); const yaml = require('yaml'); const config = yaml.parse(content); expect(config.openrouter.messageGenerationPrompt).toContain('{analysis}'); expect(config.openrouter.messageGenerationPrompt).toContain('{title}'); expect(config.openrouter.messageGenerationPrompt).toContain('{author}'); }); it('all substitution variables are in {variable} format', () => { const content = fs.readFileSync(configPath, 'utf-8'); const yaml = require('yaml'); const config = yaml.parse(content); const variables = config.openrouter.messageGenerationPrompt.match(/\{(\w+)\}/g); expect(variables).toEqual(expect.arrayContaining(['{analysis}', '{title}', '{author}'])); }); }); describe('prompt quality checks', () => { it('messageGenerationPrompt is not empty', () => { const content = fs.readFileSync(configPath, 'utf-8'); const yaml = require('yaml'); const config = yaml.parse(content); expect(config.openrouter.messageGenerationPrompt.trim().length).toBeGreaterThan(50); }); it('imageAnalysisPrompt is not empty', () => { const content = fs.readFileSync(configPath, 'utf-8'); const yaml = require('yaml'); const config = yaml.parse(content); expect(config.openrouter.imageAnalysisPrompt.trim().length).toBeGreaterThan(50); }); it('messageGenerationPrompt avoids anchoring on one example shape', () => { const content = fs.readFileSync(configPath, 'utf-8'); const yaml = require('yaml'); const config = yaml.parse(content); expect(config.openrouter.messageGenerationPrompt).not.toContain('Examples of good messages'); expect(config.openrouter.messageGenerationPrompt).toContain('Default to opening with "Damn"'); expect(config.openrouter.messageGenerationPrompt).toContain('Follow the selected STYLE exactly'); }); it('imageAnalysisPrompt requests a rating', () => { const content = fs.readFileSync(configPath, 'utf-8'); const yaml = require('yaml'); const config = yaml.parse(content); expect(config.openrouter.imageAnalysisPrompt).toContain('rate her attractiveness'); expect(config.openrouter.imageAnalysisPrompt).toContain('1-100 scale'); expect(config.openrouter.imageAnalysisPrompt).toContain('RATING:'); }); }); describe('seller keywords', () => { function loadParsedConfig() { const content = fs.readFileSync(configPath, 'utf-8'); const yaml = require('yaml'); const rawConfig = yaml.parse(content); rawConfig.openrouter = rawConfig.openrouter || {}; rawConfig.openrouter.apiKey = rawConfig.openrouter.apiKey || 'test'; return ConfigSchema.parse(rawConfig); } it('reddit sellerKeywords includes common platforms', () => { const config = loadParsedConfig(); const keywords = config.reddit.sellerKeywords.map((k: string) => k.toLowerCase()); expect(keywords).toContain('onlyfans'); expect(keywords).toContain('fansly'); expect(keywords).toContain('manyvids'); expect(keywords).toContain('cash.app'); expect(keywords).toContain('venmo'); expect(keywords).toContain('telegram'); expect(config.reddit.sellerDomains).toContain('t.me'); expect(config.reddit.sellerDomains).toContain('telegram.me'); }); }); }); function parseYamlPrompts(content: string): { imageAnalysisPrompt: string; messageGenerationPrompt: string } { const yaml = require('yaml'); const config = yaml.parse(content); return { imageAnalysisPrompt: config.openrouter.imageAnalysisPrompt, messageGenerationPrompt: config.openrouter.messageGenerationPrompt, }; }