refactor(settings): remove enableDestructiveDeleteActions runtime flag and related UI
Eliminate the enableDestructiveDeleteActions feature flag from runtime configuration, admin panel, environment docs, and user settings modal. Remove all references, toggles, and documentation for this flag. This simplifies the runtime config and user interface by consolidating destructive actions under account deletion only. BREAKING CHANGE: The enableDestructiveDeleteActions runtime flag is no longer supported. Any configuration or code depending on this flag must be updated.
This commit is contained in:
parent
e295ee4103
commit
28f8d50c05
15 changed files with 151 additions and 91 deletions
|
|
@ -87,4 +87,4 @@ S3_BUCKET=
|
|||
# (Optional) v4 JSON seed for first-boot runtime config + shared providers.
|
||||
# If both are set, RUNTIME_SEED_JSON_PATH is used.
|
||||
# RUNTIME_SEED_JSON_PATH=/absolute/path/to/openreader-seed.json
|
||||
# RUNTIME_SEED_JSON={"version":1,"runtimeConfig":{"enableUserSignups":true,"restrictUserApiKeys":true,"defaultTtsProvider":"custom-openai","enableTtsProvidersTab":true,"enableAudiobookExport":true,"enableDocxConversion":true,"enableDestructiveDeleteActions":true,"showAllProviderModels":true,"disableTtsRateLimit":true,"ttsDailyLimitAnonymous":50000,"ttsDailyLimitAuthenticated":500000,"ttsIpDailyLimitAnonymous":100000,"ttsIpDailyLimitAuthenticated":1000000,"ttsCacheMaxSizeBytes":268435456,"ttsCacheTtlMs":1800000,"ttsUpstreamMaxRetries":2,"ttsUpstreamTimeoutMs":285000,"disableComputeRateLimit":true,"computeParseBurstMax":8,"computeParseBurstWindowSec":60,"computeParseSustainedMax":24,"computeParseSustainedWindowSec":600,"maxUploadMb":200,"changelogFeedUrl":"https://docs.openreader.richardr.dev/changelog/manifest.json"},"providers":[{"slug":"default-openai","displayName":"Default (seeded)","providerType":"custom-openai","baseUrl":"http://localhost:8880/v1","apiKey":"api_key_optional","defaultModel":"kokoro","enabled":true}]}
|
||||
# RUNTIME_SEED_JSON={"version":1,"runtimeConfig":{"enableUserSignups":true,"restrictUserApiKeys":true,"defaultTtsProvider":"custom-openai","enableTtsProvidersTab":true,"enableAudiobookExport":true,"enableDocxConversion":true,"showAllProviderModels":true,"disableTtsRateLimit":true,"ttsDailyLimitAnonymous":50000,"ttsDailyLimitAuthenticated":500000,"ttsIpDailyLimitAnonymous":100000,"ttsIpDailyLimitAuthenticated":1000000,"ttsCacheMaxSizeBytes":268435456,"ttsCacheTtlMs":1800000,"ttsUpstreamMaxRetries":2,"ttsUpstreamTimeoutMs":285000,"disableComputeRateLimit":true,"computeParseBurstMax":8,"computeParseBurstWindowSec":60,"computeParseSustainedMax":24,"computeParseSustainedWindowSec":600,"maxUploadMb":200,"changelogFeedUrl":"https://docs.openreader.richardr.dev/changelog/manifest.json"},"providers":[{"slug":"default-openai","displayName":"Default (seeded)","providerType":"custom-openai","baseUrl":"http://localhost:8880/v1","apiKey":"api_key_optional","defaultModel":"kokoro","enabled":true}]}
|
||||
|
|
|
|||
|
|
@ -76,7 +76,6 @@ Runtime-editable settings, one row per key:
|
|||
| `showAllProviderModels` | When `false`, users are restricted to each provider's default model (shared provider `defaultModel` or built-in provider default). |
|
||||
| `enableAudiobookExport` | Show the audiobook export entry points on PDF/EPUB pages. |
|
||||
| `enableDocxConversion` | Accept .docx uploads (converted to PDF server-side). |
|
||||
| `enableDestructiveDeleteActions` | Show "Delete all data" buttons in the Documents tab (auth-disabled mode). |
|
||||
|
||||
Word-by-word highlighting and PDF layout parsing capability are controlled by compute-worker server env configuration, not an admin runtime flag.
|
||||
|
||||
|
|
|
|||
|
|
@ -78,7 +78,6 @@ After the first successful deploy and admin login, open **Settings → Admin** a
|
|||
- **Shared providers**: create/edit your provider key(s) here (encrypted at rest).
|
||||
- **Site features**:
|
||||
- `enableDocxConversion=false` on Vercel (`soffice` unavailable).
|
||||
- `enableDestructiveDeleteActions=false` for safer public deployments.
|
||||
- `enableTtsProvidersTab=false` if you want shared-provider-only UX.
|
||||
- `enableUserSignups=true` unless you explicitly want an invite-only deployment.
|
||||
- `restrictUserApiKeys=true` to block user BYOK through the hosted server.
|
||||
|
|
|
|||
|
|
@ -414,7 +414,6 @@ Example:
|
|||
"enableTtsProvidersTab": true,
|
||||
"enableAudiobookExport": true,
|
||||
"enableDocxConversion": true,
|
||||
"enableDestructiveDeleteActions": true,
|
||||
"showAllProviderModels": true,
|
||||
"disableTtsRateLimit": true,
|
||||
"ttsDailyLimitAnonymous": 50000,
|
||||
|
|
|
|||
|
|
@ -5,7 +5,10 @@ import { db } from '@/db';
|
|||
import { documents } from '@/db/schema';
|
||||
import { requireAuthContext } from '@/lib/server/auth/auth';
|
||||
import { createOrReusePdfWorkerOperation } from '@/lib/server/compute/worker-op-create';
|
||||
import { snapshotFromWorkerState } from '@/lib/server/compute/worker-parse-state';
|
||||
import {
|
||||
documentParseStateFromWorkerState,
|
||||
snapshotFromWorkerState,
|
||||
} from '@/lib/server/compute/worker-parse-state';
|
||||
import { fetchWorkerOperationState } from '@/lib/server/compute/worker-op-state';
|
||||
import {
|
||||
documentKey,
|
||||
|
|
@ -101,8 +104,14 @@ async function finalizeFromWorkerState(input: {
|
|||
logger: ServerLogger;
|
||||
}): Promise<NextResponse> {
|
||||
const snapshot = snapshotFromWorkerState(input.workerState);
|
||||
const workerParseState = documentParseStateFromWorkerState(input.workerState);
|
||||
|
||||
if (snapshot.parseStatus === 'pending' || snapshot.parseStatus === 'running') {
|
||||
await writeParseRowState({
|
||||
documentId: input.row.id,
|
||||
userId: input.row.userId,
|
||||
parseState: stringifyDocumentParseState(workerParseState),
|
||||
});
|
||||
return NextResponse.json({
|
||||
parseStatus: snapshot.parseStatus,
|
||||
parseProgress: snapshot.parseProgress,
|
||||
|
|
@ -114,12 +123,7 @@ async function finalizeFromWorkerState(input: {
|
|||
await writeParseRowState({
|
||||
documentId: input.row.id,
|
||||
userId: input.row.userId,
|
||||
parseState: stringifyDocumentParseState({
|
||||
status: 'failed',
|
||||
progress: null,
|
||||
updatedAt: Date.now(),
|
||||
error: input.workerState.error?.message ?? 'Worker parse failed',
|
||||
}),
|
||||
parseState: stringifyDocumentParseState(workerParseState),
|
||||
});
|
||||
|
||||
return NextResponse.json({
|
||||
|
|
@ -152,11 +156,7 @@ async function finalizeFromWorkerState(input: {
|
|||
await writeParseRowState({
|
||||
documentId: input.row.id,
|
||||
userId: input.row.userId,
|
||||
parseState: stringifyDocumentParseState({
|
||||
status: 'ready',
|
||||
progress: null,
|
||||
updatedAt: Date.now(),
|
||||
}),
|
||||
parseState: stringifyDocumentParseState(workerParseState),
|
||||
parsedJsonKey,
|
||||
});
|
||||
|
||||
|
|
@ -278,6 +278,11 @@ export async function GET(req: NextRequest, ctx: { params: Promise<{ id: string
|
|||
});
|
||||
await recordJobEvent(authCtxOrRes.userId, 'pdf_layout', created.opId, rateConfig);
|
||||
const snapshot = snapshotFromWorkerState(created);
|
||||
await writeParseRowState({
|
||||
documentId: row.id,
|
||||
userId: row.userId,
|
||||
parseState: stringifyDocumentParseState(documentParseStateFromWorkerState(created)),
|
||||
});
|
||||
return NextResponse.json({
|
||||
parseStatus: snapshot.parseStatus,
|
||||
parseProgress: snapshot.parseProgress,
|
||||
|
|
@ -409,6 +414,11 @@ export async function POST(req: NextRequest, ctx: { params: Promise<{ id: string
|
|||
await recordJobEvent(authCtxOrRes.userId, 'pdf_layout', created.opId, rateConfig);
|
||||
|
||||
const snapshot = snapshotFromWorkerState(created);
|
||||
await writeParseRowState({
|
||||
documentId: row.id,
|
||||
userId: row.userId,
|
||||
parseState: stringifyDocumentParseState(documentParseStateFromWorkerState(created)),
|
||||
});
|
||||
|
||||
return NextResponse.json({
|
||||
parseStatus: snapshot.parseStatus,
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@ import { useAuthSession } from '@/hooks/useAuthSession';
|
|||
import { useAuthConfig } from '@/contexts/AuthRateLimitContext';
|
||||
import { useRouter } from 'next/navigation';
|
||||
import { showPrivacyModal } from '@/components/PrivacyModal';
|
||||
import { deleteDocuments, mimeTypeForDoc, uploadDocuments } from '@/lib/client/api/documents';
|
||||
import { mimeTypeForDoc, uploadDocuments } from '@/lib/client/api/documents';
|
||||
import { cacheStoredDocumentFromBytes, clearDocumentCache } from '@/lib/client/cache/documents';
|
||||
import { clearAllDocumentPreviewCaches, clearInMemoryDocumentPreviewCache } from '@/lib/client/cache/previews';
|
||||
import { resolveTtsSettingsViewModel } from '@/lib/client/settings/tts-settings';
|
||||
|
|
@ -222,7 +222,6 @@ export function SettingsModal({
|
|||
onOpenChange: (open: boolean) => void;
|
||||
}) {
|
||||
const runtimeConfig = useRuntimeConfig();
|
||||
const enableDestructiveDelete = runtimeConfig.enableDestructiveDeleteActions;
|
||||
const showAllProviderModels = runtimeConfig.showAllProviderModels;
|
||||
const enableTTSProvidersTab = runtimeConfig.enableTtsProvidersTab;
|
||||
const restrictUserApiKeys = runtimeConfig.restrictUserApiKeys;
|
||||
|
|
@ -258,7 +257,6 @@ export function SettingsModal({
|
|||
const [showProgress, setShowProgress] = useState(false);
|
||||
const [statusMessage, setStatusMessage] = useState('');
|
||||
const [abortController, setAbortController] = useState<AbortController | null>(null);
|
||||
const [showDeleteDocsConfirm, setShowDeleteDocsConfirm] = useState(false);
|
||||
const [showDeleteAccountConfirm, setShowDeleteAccountConfirm] = useState(false);
|
||||
const { progress, setProgress, estimatedTimeRemaining } = useTimeEstimation();
|
||||
const { baseUrl: authBaseUrl } = useAuthConfig();
|
||||
|
|
@ -441,17 +439,6 @@ export function SettingsModal({
|
|||
}
|
||||
};
|
||||
|
||||
const handleDeleteDocs = async () => {
|
||||
try {
|
||||
await deleteDocuments();
|
||||
await refreshDocuments().catch(() => { });
|
||||
} catch (error) {
|
||||
console.error('Delete failed:', error);
|
||||
} finally {
|
||||
setShowDeleteDocsConfirm(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handleSignOut = async () => {
|
||||
const client = getAuthClient(authBaseUrl);
|
||||
await client.signOut();
|
||||
|
|
@ -1189,21 +1176,19 @@ export function SettingsModal({
|
|||
Disconnect account
|
||||
</Button>
|
||||
|
||||
{enableDestructiveDelete && (
|
||||
<div className="pt-4 mt-4 border-t border-line-soft">
|
||||
<label className="block text-sm font-medium text-danger mb-2">Danger Zone</label>
|
||||
<Button
|
||||
onClick={() => setShowDeleteAccountConfirm(true)}
|
||||
variant="danger"
|
||||
size="md"
|
||||
>
|
||||
Delete Account
|
||||
</Button>
|
||||
<p className="text-xs text-soft mt-2">
|
||||
Permanently deletes your account and all data.
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
<div className="pt-4 mt-4 border-t border-line-soft">
|
||||
<label className="block text-sm font-medium text-danger mb-2">Danger Zone</label>
|
||||
<Button
|
||||
onClick={() => setShowDeleteAccountConfirm(true)}
|
||||
variant="danger"
|
||||
size="md"
|
||||
>
|
||||
Delete Account
|
||||
</Button>
|
||||
<p className="text-xs text-soft mt-2">
|
||||
Permanently deletes your account and all data.
|
||||
</p>
|
||||
</div>
|
||||
</>
|
||||
) : (
|
||||
<div className="pt-2 border-t border-line-soft">
|
||||
|
|
@ -1246,16 +1231,6 @@ export function SettingsModal({
|
|||
)}
|
||||
</ModalFrame>
|
||||
|
||||
<ConfirmDialog
|
||||
isOpen={showDeleteDocsConfirm}
|
||||
onClose={() => setShowDeleteDocsConfirm(false)}
|
||||
onConfirm={handleDeleteDocs}
|
||||
title="Delete All Data"
|
||||
message="Are you sure you want to delete all documents from the server? This action cannot be undone."
|
||||
confirmText="Delete"
|
||||
isDangerous={true}
|
||||
/>
|
||||
|
||||
<ConfirmDialog
|
||||
isOpen={showDeleteAccountConfirm}
|
||||
onClose={() => setShowDeleteAccountConfirm(false)}
|
||||
|
|
|
|||
|
|
@ -495,14 +495,6 @@ export function AdminFeaturesPanel() {
|
|||
right={renderSource('enableDocxConversion')}
|
||||
variant="flat"
|
||||
/>
|
||||
<ToggleRow
|
||||
label="Destructive delete buttons"
|
||||
description='Show "Delete all data" actions (auth-off mode).'
|
||||
checked={Boolean(draft.enableDestructiveDeleteActions)}
|
||||
onChange={(checked) => updateDraft('enableDestructiveDeleteActions', checked)}
|
||||
right={renderSource('enableDestructiveDeleteActions')}
|
||||
variant="flat"
|
||||
/>
|
||||
</Section>
|
||||
|
||||
<Section
|
||||
|
|
|
|||
|
|
@ -20,7 +20,6 @@ export interface RuntimeConfig {
|
|||
enableTtsProvidersTab: boolean;
|
||||
enableAudiobookExport: boolean;
|
||||
enableDocxConversion: boolean;
|
||||
enableDestructiveDeleteActions: boolean;
|
||||
showAllProviderModels: boolean;
|
||||
disableTtsRateLimit: boolean;
|
||||
ttsDailyLimitAnonymous: number;
|
||||
|
|
@ -43,7 +42,6 @@ const RUNTIME_DEFAULTS: RuntimeConfig = {
|
|||
enableTtsProvidersTab: true,
|
||||
enableAudiobookExport: true,
|
||||
enableDocxConversion: true,
|
||||
enableDestructiveDeleteActions: true,
|
||||
showAllProviderModels: true,
|
||||
disableTtsRateLimit: true,
|
||||
ttsDailyLimitAnonymous: 50_000,
|
||||
|
|
|
|||
|
|
@ -71,7 +71,6 @@ export const RUNTIME_CONFIG_SCHEMA = {
|
|||
enableTtsProvidersTab: booleanFlag(true),
|
||||
enableAudiobookExport: booleanFlag(true),
|
||||
enableDocxConversion: booleanFlag(true),
|
||||
enableDestructiveDeleteActions: booleanFlag(true),
|
||||
showAllProviderModels: runtimeBoolean(true),
|
||||
disableTtsRateLimit: booleanFlag(true),
|
||||
ttsDailyLimitAnonymous: positiveIntValue(50_000),
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import type { PdfLayoutJobResult, WorkerOperationState } from '@openreader/compute-core/api-contracts';
|
||||
import type { PdfParseProgress, PdfParseStatus } from '@/types/parsed-pdf';
|
||||
import type { DocumentParseState } from '@/lib/server/documents/parse-state';
|
||||
|
||||
export function mapWorkerStatusToParseStatus(status: WorkerOperationState['status']): PdfParseStatus {
|
||||
switch (status) {
|
||||
|
|
@ -26,6 +27,23 @@ export function snapshotFromWorkerState(
|
|||
};
|
||||
}
|
||||
|
||||
export function documentParseStateFromWorkerState(
|
||||
state: WorkerOperationState<PdfLayoutJobResult>,
|
||||
nowMs = Date.now(),
|
||||
): DocumentParseState {
|
||||
const { parseStatus, parseProgress } = snapshotFromWorkerState(state);
|
||||
return {
|
||||
status: parseStatus,
|
||||
progress: parseStatus === 'pending' || parseStatus === 'running'
|
||||
? parseProgress
|
||||
: null,
|
||||
updatedAt: nowMs,
|
||||
...(typeof state.opId === 'string' && state.opId.trim() ? { opId: state.opId } : {}),
|
||||
...(typeof state.jobId === 'string' && state.jobId.trim() ? { jobId: state.jobId } : {}),
|
||||
...(parseStatus === 'failed' && state.error?.message ? { error: state.error.message } : {}),
|
||||
};
|
||||
}
|
||||
|
||||
export function mergeNonReadyParseSnapshot(input: {
|
||||
parseStatus: PdfParseStatus;
|
||||
parseProgress: PdfParseProgress | null;
|
||||
|
|
|
|||
|
|
@ -22,6 +22,8 @@ export async function healStaleDocumentParseState(input: {
|
|||
progress: null,
|
||||
updatedAt: Date.now(),
|
||||
error: `Parse state stale for more than ${staleMs}ms; marked failed for retry`,
|
||||
...(input.state.opId ? { opId: input.state.opId } : {}),
|
||||
...(input.state.jobId ? { jobId: input.state.jobId } : {}),
|
||||
}));
|
||||
|
||||
await db
|
||||
|
|
|
|||
|
|
@ -255,9 +255,8 @@ export async function setupTest(page: Page, testInfo?: TestInfo) {
|
|||
}
|
||||
});
|
||||
|
||||
// If auth is enabled, establish an anonymous session BEFORE navigation.
|
||||
// This keeps each test self-contained (no shared storageState) while ensuring
|
||||
// server routes that require auth don't intermittently 401 during app startup.
|
||||
// If we explicitly choose to bootstrap anonymous sessions for a test run,
|
||||
// do it before navigation so protected startup routes do not intermittently 401.
|
||||
// await ensureAnonymousSession(page);
|
||||
|
||||
// Navigate to the protected app home before each test
|
||||
|
|
@ -412,22 +411,6 @@ export async function openSettingsDocumentsTab(page: Page) {
|
|||
await settingsDialog.getByRole('button', { name: /^Documents$/ }).click();
|
||||
}
|
||||
|
||||
// Delete all local documents through Settings and close dialogs
|
||||
export async function deleteAllLocalDocuments(page: Page) {
|
||||
await openSettingsDocumentsTab(page);
|
||||
await page.getByRole('button', { name: /Delete all data/i }).click();
|
||||
|
||||
const heading = page.getByRole('heading', { name: /Delete All Data/i });
|
||||
await expect(heading).toBeVisible({ timeout: 10000 });
|
||||
|
||||
const confirmBtn = heading.locator('xpath=ancestor::*[@role="dialog"][1]//button[normalize-space()="Delete"]');
|
||||
await confirmBtn.click();
|
||||
|
||||
// Close any remaining modal layers
|
||||
await page.keyboard.press('Escape');
|
||||
await page.keyboard.press('Escape');
|
||||
}
|
||||
|
||||
// Open the Voices dropdown from the TTS bar and return the button locator
|
||||
export async function openVoicesMenu(page: Page) {
|
||||
const ttsbar = page.locator('[data-app-ttsbar]');
|
||||
|
|
|
|||
|
|
@ -2,8 +2,6 @@ import { expect, test } from '@playwright/test';
|
|||
|
||||
import { setupTest, uploadAndDisplay } from './helpers';
|
||||
|
||||
const AUTH_ENABLED = Boolean(process.env.AUTH_SECRET && process.env.BASE_URL);
|
||||
|
||||
test.describe('Landing and app routing', () => {
|
||||
test('public landing renders without anonymous auth bootstrap call', async ({ page }) => {
|
||||
let anonymousSignInCalls = 0;
|
||||
|
|
@ -26,8 +24,6 @@ test.describe('Landing and app routing', () => {
|
|||
});
|
||||
|
||||
test('existing authenticated session visiting / redirects to /app', async ({ page }) => {
|
||||
test.skip(!AUTH_ENABLED);
|
||||
|
||||
await page.goto('/app');
|
||||
await page.waitForLoadState('networkidle');
|
||||
await page.waitForSelector('.fixed.inset-0.bg-base.z-50', { state: 'detached', timeout: 15000 }).catch(() => {});
|
||||
|
|
@ -46,7 +42,7 @@ test.describe('Landing and app routing', () => {
|
|||
});
|
||||
|
||||
test('protected app routes redirect to /signin when anonymous auth is disabled', async ({ page }) => {
|
||||
test.skip(!AUTH_ENABLED || process.env.USE_ANONYMOUS_AUTH_SESSIONS !== 'false');
|
||||
test.skip(process.env.USE_ANONYMOUS_AUTH_SESSIONS !== 'false');
|
||||
|
||||
await page.goto('/app');
|
||||
await expect(page).toHaveURL(/\/signin$/);
|
||||
|
|
|
|||
|
|
@ -234,7 +234,6 @@ describe('runtime config JSON seeding', () => {
|
|||
enableTtsProvidersTab: false,
|
||||
enableAudiobookExport: false,
|
||||
enableDocxConversion: false,
|
||||
enableDestructiveDeleteActions: false,
|
||||
showAllProviderModels: false,
|
||||
disableTtsRateLimit: false,
|
||||
ttsDailyLimitAnonymous: 12345,
|
||||
|
|
|
|||
91
tests/unit/worker-parse-state.vitest.spec.ts
Normal file
91
tests/unit/worker-parse-state.vitest.spec.ts
Normal file
|
|
@ -0,0 +1,91 @@
|
|||
import { describe, expect, test } from 'vitest';
|
||||
import type { PdfLayoutJobResult, WorkerOperationState } from '@openreader/compute-core/api-contracts';
|
||||
import {
|
||||
documentParseStateFromWorkerState,
|
||||
snapshotFromWorkerState,
|
||||
} from '../../src/lib/server/compute/worker-parse-state';
|
||||
|
||||
function makeWorkerState(
|
||||
overrides: Partial<WorkerOperationState<PdfLayoutJobResult>>,
|
||||
): WorkerOperationState<PdfLayoutJobResult> {
|
||||
return {
|
||||
opId: 'op-123',
|
||||
opKey: 'pdf_layout|v1|doc-1',
|
||||
kind: 'pdf_layout',
|
||||
jobId: 'job-123',
|
||||
status: 'queued',
|
||||
queuedAt: 1_700_000_000_000,
|
||||
updatedAt: 1_700_000_000_000,
|
||||
...overrides,
|
||||
};
|
||||
}
|
||||
|
||||
describe('worker parse state mapping', () => {
|
||||
test('maps queued worker state to pending parse state with op identifiers', () => {
|
||||
const workerState = makeWorkerState({
|
||||
status: 'queued',
|
||||
progress: {
|
||||
totalPages: 500,
|
||||
pagesParsed: 0,
|
||||
currentPage: 1,
|
||||
phase: 'infer',
|
||||
},
|
||||
});
|
||||
|
||||
expect(snapshotFromWorkerState(workerState)).toEqual({
|
||||
parseStatus: 'pending',
|
||||
parseProgress: null,
|
||||
});
|
||||
expect(documentParseStateFromWorkerState(workerState, 1234)).toEqual({
|
||||
status: 'pending',
|
||||
progress: null,
|
||||
updatedAt: 1234,
|
||||
opId: 'op-123',
|
||||
jobId: 'job-123',
|
||||
});
|
||||
});
|
||||
|
||||
test('maps running worker state to running parse state with progress', () => {
|
||||
const workerState = makeWorkerState({
|
||||
status: 'running',
|
||||
progress: {
|
||||
totalPages: 500,
|
||||
pagesParsed: 120,
|
||||
currentPage: 121,
|
||||
phase: 'infer',
|
||||
},
|
||||
});
|
||||
|
||||
expect(documentParseStateFromWorkerState(workerState, 5678)).toEqual({
|
||||
status: 'running',
|
||||
progress: {
|
||||
totalPages: 500,
|
||||
pagesParsed: 120,
|
||||
currentPage: 121,
|
||||
phase: 'infer',
|
||||
},
|
||||
updatedAt: 5678,
|
||||
opId: 'op-123',
|
||||
jobId: 'job-123',
|
||||
});
|
||||
});
|
||||
|
||||
test('maps failed worker state to failed parse state and preserves the worker error', () => {
|
||||
const workerState = makeWorkerState({
|
||||
status: 'failed',
|
||||
error: {
|
||||
code: 'PDF_PARSE_FAILED',
|
||||
message: 'layout model crashed',
|
||||
},
|
||||
});
|
||||
|
||||
expect(documentParseStateFromWorkerState(workerState, 9999)).toEqual({
|
||||
status: 'failed',
|
||||
progress: null,
|
||||
updatedAt: 9999,
|
||||
opId: 'op-123',
|
||||
jobId: 'job-123',
|
||||
error: 'layout model crashed',
|
||||
});
|
||||
});
|
||||
});
|
||||
Loading…
Reference in a new issue