115 lines
6.3 KiB
TypeScript
115 lines
6.3 KiB
TypeScript
import { execFile } from 'node:child_process';
|
|
import { createServer } from 'node:http';
|
|
import { mkdtemp, writeFile, rm } from 'node:fs/promises';
|
|
import path from 'node:path';
|
|
import { promisify } from 'node:util';
|
|
import { afterEach, describe, expect, it, vi } from 'vitest';
|
|
import { NextRequest } from 'next/server';
|
|
import { ProviderFactory } from '../src/providers/factory';
|
|
import { VkProvider } from '../src/providers/vk/vk-provider';
|
|
import { POST as previewPost } from '../src/app/api/posts/preview/route';
|
|
import { defaultSessionStore, SESSION_COOKIE_NAME } from '../src/lib/auth/session';
|
|
import { generalApiRateLimiter } from '../src/lib/rate-limiter';
|
|
|
|
const run = promisify(execFile);
|
|
afterEach(() => { vi.restoreAllMocks(); vi.unstubAllEnvs(); generalApiRateLimiter.reset(); });
|
|
|
|
describe('live smoke cannot accept generated VK data', () => {
|
|
it('rejects explicit mock configuration in production even with a service token', () => {
|
|
vi.stubEnv('NODE_ENV', 'production');
|
|
vi.stubEnv('USE_VK_MOCK', 'true');
|
|
vi.stubEnv('VK_SERVICE_TOKEN', 'FAKE_UNUSED_SERVICE_TOKEN');
|
|
expect(() => ProviderFactory.getVkProvider()).toThrow(/forbidden in production/);
|
|
});
|
|
|
|
it('labels an actual mock provider preview despite its SERVICE auth metadata', async () => {
|
|
vi.stubEnv('NODE_ENV', 'test');
|
|
vi.stubEnv('USE_VK_MOCK', 'true');
|
|
const id = await defaultSessionStore.createSession({ id: 'fake-smoke-mode-user', vkUserId: '9001' });
|
|
try {
|
|
const response = await previewPost(new NextRequest('http://localhost/api/posts/preview', {
|
|
method: 'POST', headers: { cookie: `${SESSION_COOKIE_NAME}=${id}`, 'content-type': 'application/json' },
|
|
body: JSON.stringify({ url: 'https://vk.com/wall-1_1' }),
|
|
}));
|
|
const body = await response.json();
|
|
expect(response.status).toBe(200);
|
|
expect(body.effectiveCapabilities.accessMode).toBe('PUBLIC_SERVICE');
|
|
expect(body.providerMode).toBe('MOCK');
|
|
} finally { await defaultSessionStore.destroySession(id); }
|
|
});
|
|
|
|
it('labels a real provider instance without exposing its token', async () => {
|
|
vi.stubEnv('NODE_ENV', 'test');
|
|
vi.stubEnv('USE_VK_MOCK', 'false');
|
|
vi.stubEnv('VK_SERVICE_TOKEN', 'FAKE_LIVE_MODE_MARKER_TOKEN');
|
|
vi.spyOn(VkProvider.prototype, 'fetchPost').mockResolvedValue({
|
|
platform: 'VK', ownerId: '-1', postId: '1', sourceUrl: 'https://vk.com/wall-1_1',
|
|
title: 'Fake response', text: '', likesCount: 2, commentsCount: 0, repostsCount: 0, resolvedAuthType: 'SERVICE',
|
|
});
|
|
const id = await defaultSessionStore.createSession({ id: 'fake-live-mode-user', vkUserId: '9002' });
|
|
try {
|
|
const response = await previewPost(new NextRequest('http://localhost/api/posts/preview', {
|
|
method: 'POST', headers: { cookie: `${SESSION_COOKIE_NAME}=${id}`, 'content-type': 'application/json' },
|
|
body: JSON.stringify({ url: 'https://vk.com/wall-1_1' }),
|
|
}));
|
|
const text = await response.text();
|
|
expect(response.status).toBe(200);
|
|
expect(JSON.parse(text).providerMode).toBe('LIVE');
|
|
expect(text).not.toContain('FAKE_LIVE_MODE_MARKER_TOKEN');
|
|
} finally { await defaultSessionStore.destroySession(id); }
|
|
});
|
|
|
|
it.each([
|
|
{ mode: 'MOCK', count: 2, expected: 1 },
|
|
{ mode: 'LIVE', count: 0, expected: 1 },
|
|
{ mode: 'LIVE', count: 2, expected: 0 },
|
|
])('smoke exits $expected for $mode mode with $count participants', async ({ mode, count, expected }) => {
|
|
let createCalls = 0;
|
|
let importCalls = 0;
|
|
const server = createServer((req, res) => {
|
|
req.resume();
|
|
if (req.url === '/api/auth/vk/start') {
|
|
res.writeHead(307, { location: 'https://id.vk.com/authorize?code_challenge_method=S256' }); res.end(); return;
|
|
}
|
|
res.setHeader('content-type', 'application/json');
|
|
if (req.url === '/api/auth/me') { res.end(JSON.stringify({ authenticated: true, user: { id: 'fake-user' } })); return; }
|
|
if (req.url === '/api/posts/preview') {
|
|
res.end(JSON.stringify({ success: true, providerMode: mode,
|
|
effectiveCapabilities: { accessMode: 'PUBLIC_SERVICE' }, post: { sourceUrl: 'https://vk.com/wall-1_1', resolvedAuthType: 'SERVICE' } })); return;
|
|
}
|
|
if (req.url === '/api/giveaways') {
|
|
createCalls++; res.statusCode = 201; res.end(JSON.stringify({ success: true, giveaway: { id: 'fake-giveaway' } })); return;
|
|
}
|
|
if (req.url === '/api/giveaways/fake-giveaway/participants') {
|
|
importCalls++; res.end(JSON.stringify({ success: true, providerMode: mode, totalCount: count })); return;
|
|
}
|
|
res.statusCode = 404; res.end('{}');
|
|
});
|
|
await new Promise<void>(resolve => server.listen(0, '127.0.0.1', resolve));
|
|
// Keep all local scratch material under the agent's permitted notes area.
|
|
const dir = await mkdtemp(path.resolve('agents/codex/notes/.smoke-regression-'));
|
|
try {
|
|
const port = (server.address() as { port: number }).port;
|
|
const file = path.join(dir, '.env.local');
|
|
await writeFile(file, `VK_SMOKE_BASE_URL=http://127.0.0.1:${port}\nVK_SMOKE_SESSION_COOKIE=randomayzer_session=FAKE_SMOKE_COOKIE\nVK_SMOKE_PUBLIC_POST_URL=https://vk.com/wall-1_1\nVK_SMOKE_GROUP_ID=42\nVK_SMOKE_CREATE_GIVEAWAY=YES\n`, { mode: 0o600 });
|
|
let stdout = '', exitCode = 0;
|
|
try { stdout = (await run('bash', [path.resolve('tools/vk-smoke.sh'), file], { timeout: 20000 })).stdout; }
|
|
catch (error) { const failure = error as { code: number; stdout: string }; exitCode = failure.code; stdout = failure.stdout; }
|
|
expect(exitCode).toBe(expected);
|
|
expect(stdout).toContain('MANUAL B');
|
|
expect(stdout).not.toContain('FAKE_SMOKE_COOKIE');
|
|
if (mode === 'MOCK') {
|
|
expect(stdout).toContain('FAIL D'); expect(stdout).not.toContain('PASS G'); expect(stdout).not.toContain('PASS H');
|
|
expect(createCalls).toBe(0); expect(importCalls).toBe(0);
|
|
} else if (count === 0) {
|
|
expect(stdout).toContain('FAIL G'); expect(stdout).toContain('FAIL H');
|
|
} else {
|
|
expect(stdout).toContain('PASS D'); expect(stdout).toContain('PASS G'); expect(stdout).toContain('PASS H');
|
|
expect(importCalls).toBe(2);
|
|
}
|
|
} finally {
|
|
await rm(dir, { recursive: true, force: true });
|
|
await new Promise<void>((resolve, reject) => server.close(err => err ? reject(err) : resolve()));
|
|
}
|
|
});
|
|
});
|