Files

267 lines
11 KiB
TypeScript

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,
};
}