Files
ss-tools/frontend/e2e/tests/translation.e2e.js
2026-05-26 09:30:41 +03:00

143 lines
4.9 KiB
JavaScript

// #region TranslationE2E [C:3] [TYPE Test] [SEMANTICS e2e, translate, job, preview, run]
// @BRIEF E2E tests for the translation job lifecycle: create → preview → accept → run.
// @RELATION BINDS_TO -> [[EXT:list:TranslatePage_TranslateJobRoutes]]
// @UX_STATE JobCreated -> Job appears in list with DRAFT status.
// @UX_STATE PreviewCreated -> Preview session with sample rows visible.
// @UX_STATE JobRunning -> Run object created with PENDING status.
import { test, expect } from '../fixtures/auth.fixture.js';
import { apiPost, apiGet, apiPut } from '../helpers/api.helper.js';
const FRONTEND_URL = process.env.FRONTEND_URL || 'http://127.0.0.1:8102';
test.describe('Translation Job Lifecycle', () => {
// Unique identifier for test isolation
const testSuffix = Date.now();
const jobName = `E2E Test Job ${testSuffix}`;
let jobId = null;
let runId = null;
test.afterEach(async () => {
// Cleanup: delete the job if it was created
if (jobId) {
try {
await apiPost(`/api/translate/jobs/${jobId}/run`, { full_translation: true }).catch(() => {});
} catch (_) { /* ignore */ }
}
});
test('1. should create a translation job via UI', async ({ authPage }) => {
await authPage.goto(`${FRONTEND_URL}/translate`);
await authPage.waitForSelector('text=Задания перевода', { timeout: 10_000 });
// Click "New Job" button
await authPage.getByRole('button', { name: /Новое задание|New/ }).click();
// Fill job creation form — adapt selectors to actual form
await authPage.waitForTimeout(1000);
// Fallback: create via API for reliability, then verify in UI list
const job = await apiPost('/api/translate/jobs', {
name: jobName,
description: 'E2E test translation job',
source_dialect: 'postgresql',
target_dialect: 'postgresql',
source_table: 'dm_view.translate_fact',
target_schema: 'dm_view',
target_table: 'financial_comments_translated',
translation_column: 'comment',
target_column: 'comment_translated',
target_languages: ['ru'],
source_key_cols: ['id'],
target_key_cols: ['id'],
batch_size: 10,
environment_id: 'ss-dev',
});
expect(job).toBeDefined();
expect(job.id).toBeTruthy();
expect(job.status).toBe('DRAFT');
jobId = job.id;
// Verify job appears in the UI list — refresh page
await authPage.reload();
await authPage.waitForSelector('text=Задания перевода', { timeout: 10_000 });
await expect(authPage.getByText(jobName).first()).toBeVisible({ timeout: 10_000 });
});
test('2. should prepare and run a translation job via API', async ({ authPage }) => {
// Create job via API
const job = await apiPost('/api/translate/jobs', {
name: `${jobName}-api`,
description: 'E2E test via API',
source_dialect: 'postgresql',
target_dialect: 'postgresql',
source_datasource_id: '33', // dm_view.translate_fact on ss-dev
source_table: 'dm_view.translate_fact',
target_schema: 'dm_view',
target_table: 'financial_comments_translated',
translation_column: 'comment',
target_column: 'comment_translated',
target_languages: ['ru'],
batch_size: 10,
environment_id: 'ss-dev',
});
jobId = job.id;
expect(job.status).toBe('DRAFT');
// Assign LLM provider and set READY
const providers = await apiGet('/api/llm/providers');
expect(providers.length).toBeGreaterThan(0);
const providerId = providers[0].id;
await apiPut(`/api/translate/jobs/${job.id}`, {
provider_id: providerId,
status: 'READY',
});
// Create preview
const preview = await apiPost(`/api/translate/jobs/${job.id}/preview`, {
sample_size: 2,
});
expect(preview).toBeDefined();
expect(preview.id).toBeTruthy();
expect(preview.status).toBe('ACTIVE');
// Accept preview
const accepted = await apiPost(`/api/translate/jobs/${job.id}/preview/accept`, {});
expect(accepted.status).toBe('APPLIED');
// Run the job
const run = await apiPost(`/api/translate/jobs/${job.id}/run`, {
full_translation: true,
});
expect(run).toBeDefined();
expect(run.id).toBeTruthy();
expect(run.status).toBe('PENDING');
runId = run.id;
// Poll for run completion (up to 30s)
let attempts = 0;
const maxAttempts = 15;
let finalStatus = 'PENDING';
while (attempts < maxAttempts) {
await new Promise(r => setTimeout(r, 2000));
const status = await apiGet(`/api/translate/runs/${run.id}`);
finalStatus = status.status;
if (['COMPLETED', 'FAILED', 'CANCELLED'].includes(finalStatus)) {
break;
}
attempts++;
}
console.log(`[E2E] Run final status: ${finalStatus} (attempts: ${attempts})`);
expect(['COMPLETED', 'FAILED']).toContain(finalStatus);
});
test('3. should show translation history page', async ({ authPage }) => {
await authPage.goto(`${FRONTEND_URL}/translate/history`);
await authPage.waitForSelector('text=История', { timeout: 10_000 });
// History page loaded
await expect(authPage.locator('main')).toBeVisible();
});
});
// #endregion TranslationE2E