chore: something changed, commit before reorg

This commit is contained in:
Daniil
2026-04-27 23:28:28 +03:00
parent 46f34bdcac
commit 20928e9a60
16 changed files with 1967 additions and 1262 deletions
+166 -118
View File
@@ -2,12 +2,14 @@ import { expect, test } from "@playwright/test"
const USER_ID = "00000000-0000-0000-0000-000000000001"
const PROJECT_ID = "65df675b-013b-4b1f-ab2d-075dadbcd0d9"
const SOURCE_FILE_ID = "00000000-0000-0000-0000-000000000011"
const CAPTION_PRESET_ID = "00000000-0000-0000-0000-000000000010"
const TRANSCRIPTION_ARTIFACT_ID =
"00000000-0000-0000-0000-000000000020"
const TRANSCRIPTION_ID = "00000000-0000-0000-0000-000000000030"
const CAPTION_JOB_ID = "00000000-0000-0000-0000-000000000040"
const PRIMARY_FILE_KEY = "projects/test/video.mp4"
const PRIMARY_FILE_URL = "http://localhost:4444/files/video.mp4"
const DEFAULT_USER = {
id: USER_ID,
@@ -26,53 +28,49 @@ const DEFAULT_USER = {
}
test.describe("Caption Settings Step", () => {
test("should recover a missing transcription artifact from project data", async ({
test("should render from typed workspace and start caption render via workflow action", async ({
page,
}) => {
let project: Record<string, unknown> = {
id: PROJECT_ID,
owner_id: USER_ID,
name: "Тестовый проект",
description: null,
language: "auto",
folder: null,
status: "DRAFT",
workspace_state: {
wizard: {
current_step: "caption-settings",
completed_steps: [
"upload",
"verify",
"silence-settings",
"processing",
"fragments",
"transcription-settings",
"transcription-processing",
"subtitle-revision",
],
primary_file_key: PRIMARY_FILE_KEY,
video_url: "http://localhost:9000/projects/test/video.mp4",
silence_settings: {
min_silence_duration_ms: 200,
silence_threshold_db: 16,
padding_ms: 100,
},
active_job_id: null,
active_job_type: null,
silence_job_id: null,
transcription_artifact_id: null,
caption_preset_id: CAPTION_PRESET_ID,
caption_style_config: null,
captioned_video_path: null,
},
let workflowActions: Array<Record<string, unknown>> = []
let workspace: Record<string, any> = {
revision: 1,
phase: "CAPTIONS",
current_screen: "caption-settings",
active_job: null,
source_file_id: SOURCE_FILE_ID,
workspace_view: {
used_file_ids: [],
selected_file_id: null,
},
silence: {
status: "SKIPPED",
settings: {
min_silence_duration_ms: 200,
silence_threshold_db: 16,
padding_ms: 100,
},
detect_job_id: null,
detected_segments: [],
reviewed_cuts: [],
duration_ms: null,
applied_output_file_id: null,
},
transcription: {
status: "REVIEW_READY",
job_id: null,
request: null,
artifact_id: TRANSCRIPTION_ARTIFACT_ID,
transcription_id: TRANSCRIPTION_ID,
reviewed: true,
},
captions: {
status: "CONFIG_READY",
preset_id: CAPTION_PRESET_ID,
style_config: null,
render_job_id: null,
output_file_id: null,
},
is_active: true,
created_at: "2025-06-01T00:00:00Z",
updated_at: "2025-06-01T00:00:00Z",
}
let savedWizardState: Record<string, unknown> | null = null
let generateRequestBody: Record<string, unknown> | null = null
let generateRequestCount = 0
await page.context().addCookies([
{
@@ -98,37 +96,128 @@ test.describe("Caption Settings Step", () => {
})
await page.route(`**/api/projects/${PROJECT_ID}/`, async (route) => {
if (route.request().method() === "GET") {
await route.fulfill({
status: 200,
contentType: "application/json",
body: JSON.stringify(project),
})
return
}
if (route.request().method() === "PATCH") {
const body = route.request().postDataJSON() as {
workspace_state?: { wizard?: Record<string, unknown> }
}
savedWizardState = body.workspace_state?.wizard ?? null
project = {
...project,
workspace_state: body.workspace_state ?? project.workspace_state,
}
await route.fulfill({
status: 200,
contentType: "application/json",
body: JSON.stringify(project),
})
return
}
await route.fallback()
await route.fulfill({
status: 200,
contentType: "application/json",
body: JSON.stringify({
id: PROJECT_ID,
owner_id: USER_ID,
name: "Тестовый проект",
description: null,
language: "auto",
folder: null,
status: "DRAFT",
workspace_state: null,
is_active: true,
created_at: "2025-06-01T00:00:00Z",
updated_at: "2025-06-01T00:00:00Z",
}),
})
})
await page.route(`**/api/projects/${PROJECT_ID}/workspace*`, async (route) => {
await route.fulfill({
status: 200,
contentType: "application/json",
body: JSON.stringify(workspace),
})
})
await page.route(
`**/api/projects/${PROJECT_ID}/workflow/actions*`,
async (route) => {
const action = route.request().postDataJSON() as Record<string, unknown>
workflowActions.push(action)
if (action.type === "START_CAPTION_RENDER") {
workspace = {
...workspace,
revision: 2,
current_screen: "caption-processing",
active_job: {
job_id: CAPTION_JOB_ID,
job_type: "CAPTIONS_GENERATE",
},
captions: {
...workspace.captions,
status: "RUNNING",
render_job_id: CAPTION_JOB_ID,
},
}
}
await route.fulfill({
status: 200,
contentType: "application/json",
body: JSON.stringify(workspace),
})
},
)
await page.route("**/api/files/files/", async (route) => {
await route.fulfill({
status: 200,
contentType: "application/json",
body: JSON.stringify([
{
id: SOURCE_FILE_ID,
project_id: PROJECT_ID,
owner_id: USER_ID,
original_filename: "video.mp4",
path: PRIMARY_FILE_KEY,
storage_backend: "S3",
mime_type: "video/mp4",
size_bytes: 1024,
checksum: null,
file_format: "mp4",
is_uploaded: true,
is_deleted: false,
is_active: true,
created_at: "2025-06-01T00:00:00Z",
},
]),
})
})
await page.route(`**/api/files/files/${SOURCE_FILE_ID}/`, async (route) => {
await route.fulfill({
status: 200,
contentType: "application/json",
body: JSON.stringify({
id: SOURCE_FILE_ID,
project_id: PROJECT_ID,
owner_id: USER_ID,
original_filename: "video.mp4",
path: PRIMARY_FILE_KEY,
storage_backend: "S3",
mime_type: "video/mp4",
size_bytes: 1024,
checksum: null,
file_format: "mp4",
is_uploaded: true,
is_deleted: false,
is_active: true,
created_at: "2025-06-01T00:00:00Z",
}),
})
})
await page.route(
`**/api/files/files/${SOURCE_FILE_ID}/resolve/`,
async (route) => {
await route.fulfill({
status: 200,
contentType: "application/json",
body: JSON.stringify({
file_id: SOURCE_FILE_ID,
file_url: PRIMARY_FILE_URL,
file_path: PRIMARY_FILE_KEY,
filename: "video.mp4",
}),
})
},
)
await page.route("**/api/media/artifacts/", async (route) => {
await route.fulfill({
status: 200,
@@ -149,20 +238,6 @@ test.describe("Caption Settings Step", () => {
})
})
await page.route(
`**/api/transcribe/transcriptions/by-artifact/${TRANSCRIPTION_ARTIFACT_ID}/`,
async (route) => {
await route.fulfill({
status: 200,
contentType: "application/json",
body: JSON.stringify({
id: TRANSCRIPTION_ID,
artifact_id: TRANSCRIPTION_ARTIFACT_ID,
}),
})
},
)
await page.route("**/api/captions/presets/", async (route) => {
await route.fulfill({
status: 200,
@@ -183,27 +258,13 @@ test.describe("Caption Settings Step", () => {
})
})
await page.route("**/api/tasks/captions-generate/", async (route) => {
generateRequestCount += 1
generateRequestBody = route.request().postDataJSON() as Record<
string,
unknown
>
await route.fulfill({
status: 200,
contentType: "application/json",
body: JSON.stringify({ job_id: CAPTION_JOB_ID }),
})
})
await page.route("**/api/tasks/status/**", async (route) => {
await route.fulfill({
status: 200,
contentType: "application/json",
body: JSON.stringify({
status: "RUNNING",
progress_pct: 0,
progress_pct: 25,
output_data: null,
}),
})
@@ -220,26 +281,13 @@ test.describe("Caption Settings Step", () => {
await expect(captionStep.getByText("Системный пресет")).toBeVisible()
await expect(generateButton).toBeEnabled()
await expect
.poll(() => savedWizardState?.transcription_artifact_id ?? null)
.toBe(TRANSCRIPTION_ARTIFACT_ID)
await generateButton.click()
expect(generateRequestBody).toMatchObject({
video_s3_path: PRIMARY_FILE_KEY,
transcription_id: TRANSCRIPTION_ID,
project_id: PROJECT_ID,
preset_id: CAPTION_PRESET_ID,
expect(workflowActions).toHaveLength(1)
expect(workflowActions[0]).toMatchObject({
type: "START_CAPTION_RENDER",
revision: 1,
})
expect(generateRequestCount).toBe(1)
await expect
.poll(() => savedWizardState?.current_step ?? null)
.toBe("caption-processing")
await expect
.poll(() => savedWizardState?.active_job_id ?? null)
.toBe(CAPTION_JOB_ID)
await expect(page.locator("[data-testid='ProcessingStep']")).toBeVisible()
})
+257 -121
View File
@@ -2,7 +2,6 @@ import { expect, test } from "@playwright/test"
const USER_ID = "00000000-0000-0000-0000-000000000001"
const PROJECT_ID = "75df675b-013b-4b1f-ab2d-075dadbcd0d9"
const DETECT_JOB_ID = "00000000-0000-0000-0000-000000000050"
const APPLY_JOB_ID = "00000000-0000-0000-0000-000000000051"
const TRANSCRIPTION_JOB_ID = "00000000-0000-0000-0000-000000000052"
const ORIGINAL_FILE_ID = "00000000-0000-0000-0000-000000000060"
@@ -34,51 +33,50 @@ const MOCK_SEGMENTS = [
]
test.describe("Silence Apply Flow", () => {
test("should show processing for cut application and transcribe the processed video", async ({
test("should persist cuts via workflow actions and continue to transcription on processed source file", async ({
page,
}) => {
let project: Record<string, unknown> = {
id: PROJECT_ID,
owner_id: USER_ID,
name: "Тестовый проект",
description: null,
language: "auto",
folder: null,
status: "DRAFT",
workspace_state: {
wizard: {
current_step: "fragments",
completed_steps: [
"upload",
"verify",
"silence-settings",
"processing",
],
primary_file_id: ORIGINAL_FILE_ID,
primary_file_key: ORIGINAL_FILE_KEY,
original_file_name: "original-video.mp4",
silence_settings: {
min_silence_duration_ms: 200,
silence_threshold_db: 16,
padding_ms: 100,
},
active_job_id: null,
active_job_type: null,
silence_job_id: DETECT_JOB_ID,
transcription_artifact_id: null,
caption_preset_id: null,
caption_style_config: null,
captioned_video_path: null,
captioned_video_file_id: null,
},
let applyStatus: "RUNNING" | "DONE" = "RUNNING"
const workflowActions: Array<Record<string, unknown>> = []
let workspace: Record<string, any> = {
revision: 1,
phase: "SILENCE",
current_screen: "fragments",
active_job: null,
source_file_id: ORIGINAL_FILE_ID,
workspace_view: {
used_file_ids: [],
selected_file_id: null,
},
silence: {
status: "REVIEW_READY",
settings: {
min_silence_duration_ms: 200,
silence_threshold_db: 16,
padding_ms: 100,
},
detect_job_id: "00000000-0000-0000-0000-000000000050",
detected_segments: MOCK_SEGMENTS,
reviewed_cuts: [],
duration_ms: 30000,
applied_output_file_id: null,
},
transcription: {
status: "IDLE",
job_id: null,
request: null,
artifact_id: null,
transcription_id: null,
reviewed: false,
},
captions: {
status: "IDLE",
preset_id: null,
style_config: null,
render_job_id: null,
output_file_id: null,
},
is_active: true,
created_at: "2025-06-01T00:00:00Z",
updated_at: "2025-06-01T00:00:00Z",
}
let savedWizardState: Record<string, unknown> | null = null
let applyStatus = "RUNNING"
let transcriptionRequestBody: Record<string, unknown> | null = null
await page.context().addCookies([
{
@@ -104,35 +102,154 @@ test.describe("Silence Apply Flow", () => {
})
await page.route(`**/api/projects/${PROJECT_ID}/`, async (route) => {
if (route.request().method() === "GET") {
await route.fulfill({
status: 200,
contentType: "application/json",
body: JSON.stringify(project),
})
return
await route.fulfill({
status: 200,
contentType: "application/json",
body: JSON.stringify({
id: PROJECT_ID,
owner_id: USER_ID,
name: "Тестовый проект",
description: null,
language: "auto",
folder: null,
status: "DRAFT",
workspace_state: null,
is_active: true,
created_at: "2025-06-01T00:00:00Z",
updated_at: "2025-06-01T00:00:00Z",
}),
})
})
await page.route(`**/api/projects/${PROJECT_ID}/workspace*`, async (route) => {
if (
applyStatus === "DONE" &&
workspace.current_screen === "silence-apply-processing"
) {
workspace = {
...workspace,
revision: 4,
phase: "TRANSCRIPTION",
current_screen: "transcription-settings",
active_job: null,
source_file_id: CUT_FILE_ID,
silence: {
...workspace.silence,
status: "APPLIED",
applied_output_file_id: CUT_FILE_ID,
},
}
}
if (route.request().method() === "PATCH") {
const body = route.request().postDataJSON() as {
workspace_state?: { wizard?: Record<string, unknown> }
await route.fulfill({
status: 200,
contentType: "application/json",
body: JSON.stringify(workspace),
})
})
await page.route(
`**/api/projects/${PROJECT_ID}/workflow/actions*`,
async (route) => {
const action = route.request().postDataJSON() as Record<string, unknown>
workflowActions.push(action)
if (action.type === "SET_SILENCE_CUTS") {
workspace = {
...workspace,
revision: 2,
silence: {
...workspace.silence,
reviewed_cuts: action.cuts as typeof MOCK_SEGMENTS,
},
}
}
savedWizardState = body.workspace_state?.wizard ?? null
project = {
...project,
workspace_state: body.workspace_state ?? project.workspace_state,
if (action.type === "START_SILENCE_APPLY") {
workspace = {
...workspace,
revision: 3,
current_screen: "silence-apply-processing",
active_job: {
job_id: APPLY_JOB_ID,
job_type: "SILENCE_APPLY",
},
silence: {
...workspace.silence,
status: "APPLYING",
},
}
}
if (action.type === "START_TRANSCRIPTION") {
workspace = {
...workspace,
revision: 5,
current_screen: "transcription-processing",
active_job: {
job_id: TRANSCRIPTION_JOB_ID,
job_type: "TRANSCRIPTION_GENERATE",
},
transcription: {
...workspace.transcription,
status: "RUNNING",
job_id: TRANSCRIPTION_JOB_ID,
request: action.request as {
engine: "whisper"
language?: string
model: string
},
},
}
}
await route.fulfill({
status: 200,
contentType: "application/json",
body: JSON.stringify(project),
body: JSON.stringify(workspace),
})
return
}
},
)
await route.fallback()
await page.route("**/api/files/files/", async (route) => {
await route.fulfill({
status: 200,
contentType: "application/json",
body: JSON.stringify([
{
id: ORIGINAL_FILE_ID,
project_id: PROJECT_ID,
owner_id: USER_ID,
original_filename: "original-video.mp4",
path: ORIGINAL_FILE_KEY,
storage_backend: "S3",
mime_type: "video/mp4",
size_bytes: 1024,
checksum: null,
file_format: "mp4",
is_uploaded: true,
is_deleted: false,
is_active: true,
created_at: "2025-06-01T00:00:00Z",
},
{
id: CUT_FILE_ID,
project_id: PROJECT_ID,
owner_id: USER_ID,
original_filename: "cut-video.mp4",
path: CUT_FILE_KEY,
storage_backend: "S3",
mime_type: "video/mp4",
size_bytes: 1024,
checksum: null,
file_format: "mp4",
is_uploaded: true,
is_deleted: false,
is_active: true,
created_at: "2025-06-01T00:00:00Z",
},
]),
})
})
await page.route("**/api/files/files/*/resolve/", async (route) => {
@@ -146,30 +263,50 @@ test.describe("Silence Apply Flow", () => {
file_id: isCutFile ? CUT_FILE_ID : ORIGINAL_FILE_ID,
file_url: isCutFile ? CUT_FILE_URL : ORIGINAL_FILE_URL,
file_path: isCutFile ? CUT_FILE_KEY : ORIGINAL_FILE_KEY,
filename: isCutFile ? "cut-video.mp4" : "original-video.mp4",
}),
})
})
await page.route("**/api/files/files/*/", async (route) => {
const fileId = route.request().url().split("/files/")[1]?.split("/")[0]
const isCutFile = fileId === CUT_FILE_ID
await route.fulfill({
status: 200,
contentType: "application/json",
body: JSON.stringify({
id: isCutFile ? CUT_FILE_ID : ORIGINAL_FILE_ID,
project_id: PROJECT_ID,
owner_id: USER_ID,
original_filename: isCutFile
? "cut-video.mp4"
: "original-video.mp4",
path: isCutFile ? CUT_FILE_KEY : ORIGINAL_FILE_KEY,
storage_backend: "S3",
mime_type: "video/mp4",
size_bytes: 1024,
checksum: null,
file_format: "mp4",
is_uploaded: true,
is_deleted: false,
is_active: true,
created_at: "2025-06-01T00:00:00Z",
}),
})
})
await page.route("**/api/media/artifacts/", async (route) => {
await route.fulfill({
status: 200,
contentType: "application/json",
body: JSON.stringify([]),
})
})
await page.route("**/api/tasks/status/**", async (route) => {
const url = route.request().url()
if (url.includes(DETECT_JOB_ID)) {
await route.fulfill({
status: 200,
contentType: "application/json",
body: JSON.stringify({
status: "DONE",
job_type: "SILENCE_DETECT",
progress_pct: 100,
output_data: {
silent_segments: MOCK_SEGMENTS,
duration_ms: 30000,
},
}),
})
return
}
if (url.includes(APPLY_JOB_ID)) {
await route.fulfill({
status: 200,
@@ -190,6 +327,20 @@ test.describe("Silence Apply Flow", () => {
return
}
if (url.includes(TRANSCRIPTION_JOB_ID)) {
await route.fulfill({
status: 200,
contentType: "application/json",
body: JSON.stringify({
status: "RUNNING",
job_type: "TRANSCRIPTION_GENERATE",
progress_pct: 10,
output_data: null,
}),
})
return
}
await route.fulfill({
status: 200,
contentType: "application/json",
@@ -201,60 +352,45 @@ test.describe("Silence Apply Flow", () => {
})
})
await page.route("**/api/tasks/silence-apply/", async (route) => {
await route.fulfill({
status: 202,
contentType: "application/json",
body: JSON.stringify({ job_id: APPLY_JOB_ID }),
})
})
await page.route("**/api/tasks/transcription-generate/", async (route) => {
transcriptionRequestBody = route.request().postDataJSON() as Record<
string,
unknown
>
await route.fulfill({
status: 202,
contentType: "application/json",
body: JSON.stringify({ job_id: TRANSCRIPTION_JOB_ID }),
})
})
await page.goto(`/projects/${PROJECT_ID}`)
const fragmentsStep = page.locator("[data-testid='FragmentsStep']")
await expect(fragmentsStep).toBeVisible()
await expect(page.locator("[data-testid='FragmentsStep']")).toBeVisible()
await expect(page.locator("[data-testid='cut-region']")).toHaveCount(2)
await fragmentsStep.getByRole("button", { name: "Применить" }).click()
await page.getByRole("button", { name: "Применить" }).click()
await expect.poll(() => workflowActions.length).toBe(2)
expect(workflowActions[0]).toMatchObject({
type: "SET_SILENCE_CUTS",
revision: 1,
})
expect(workflowActions[1]).toMatchObject({
type: "START_SILENCE_APPLY",
revision: 2,
})
await expect(page.locator("[data-testid='ProcessingStep']")).toBeVisible()
await expect
.poll(() => savedWizardState?.active_job_type ?? null)
.toBe("SILENCE_APPLY")
await expect
.poll(() => savedWizardState?.current_step ?? null)
.toBe("processing")
applyStatus = "DONE"
const transcriptionStep = page.locator(
"[data-testid='TranscriptionSettingsStep']",
)
await expect(transcriptionStep).toBeVisible({ timeout: 10_000 })
await expect(
page.locator("[data-testid='TranscriptionSettingsStep']"),
).toBeVisible()
await expect
.poll(() => savedWizardState?.primary_file_key ?? null)
.toBe(CUT_FILE_KEY)
await page.getByRole("button", { name: "Сгенерировать субтитры" }).click()
await transcriptionStep
.getByRole("button", { name: "Сгенерировать субтитры" })
.click()
expect(transcriptionRequestBody).toMatchObject({
file_key: CUT_FILE_KEY,
project_id: PROJECT_ID,
expect(workflowActions[2]).toMatchObject({
type: "START_TRANSCRIPTION",
revision: 4,
request: {
engine: "whisper",
model: "base",
},
})
await expect(page.locator("[data-testid='ProcessingStep']")).toContainText(
"ТРАНСКРИБАЦИЯ",
)
})
})