Compare commits
3 Commits
main
...
d8de391c10
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d8de391c10 | ||
|
|
5dd91fa185 | ||
|
|
2ad25f069c |
@@ -52,9 +52,9 @@ public class MassImportService {
|
|||||||
|
|
||||||
public enum State { IDLE, RUNNING, DONE, FAILED }
|
public enum State { IDLE, RUNNING, DONE, FAILED }
|
||||||
|
|
||||||
public record ImportStatus(State state, String message, int processed, LocalDateTime startedAt) {}
|
public record ImportStatus(State state, String statusCode, String message, int processed, LocalDateTime startedAt) {}
|
||||||
|
|
||||||
private volatile ImportStatus currentStatus = new ImportStatus(State.IDLE, "Kein Import gestartet.", 0, null);
|
private volatile ImportStatus currentStatus = new ImportStatus(State.IDLE, "IMPORT_IDLE", "Kein Import gestartet.", 0, null);
|
||||||
|
|
||||||
public ImportStatus getStatus() {
|
public ImportStatus getStatus() {
|
||||||
return currentStatus;
|
return currentStatus;
|
||||||
@@ -116,20 +116,29 @@ public class MassImportService {
|
|||||||
if (currentStatus.state() == State.RUNNING) {
|
if (currentStatus.state() == State.RUNNING) {
|
||||||
throw DomainException.conflict(ErrorCode.IMPORT_ALREADY_RUNNING, "A mass import is already in progress");
|
throw DomainException.conflict(ErrorCode.IMPORT_ALREADY_RUNNING, "A mass import is already in progress");
|
||||||
}
|
}
|
||||||
currentStatus = new ImportStatus(State.RUNNING, "Import läuft...", 0, LocalDateTime.now());
|
currentStatus = new ImportStatus(State.RUNNING, "IMPORT_RUNNING", "Import läuft...", 0, LocalDateTime.now());
|
||||||
try {
|
try {
|
||||||
File spreadsheet = findSpreadsheetFile();
|
File spreadsheet = findSpreadsheetFile();
|
||||||
log.info("Starte Massenimport aus: {}", spreadsheet.getAbsolutePath());
|
log.info("Starte Massenimport aus: {}", spreadsheet.getAbsolutePath());
|
||||||
int processed = processRows(readSpreadsheet(spreadsheet));
|
int processed = processRows(readSpreadsheet(spreadsheet));
|
||||||
currentStatus = new ImportStatus(State.DONE,
|
currentStatus = new ImportStatus(State.DONE, "IMPORT_DONE",
|
||||||
"Import abgeschlossen. " + processed + " Dokumente verarbeitet.",
|
"Import abgeschlossen. " + processed + " Dokumente verarbeitet.",
|
||||||
processed, currentStatus.startedAt());
|
processed, currentStatus.startedAt());
|
||||||
|
} catch (NoSpreadsheetException e) {
|
||||||
|
log.error("Massenimport fehlgeschlagen: keine Tabellendatei", e);
|
||||||
|
currentStatus = new ImportStatus(State.FAILED, "IMPORT_FAILED_NO_SPREADSHEET",
|
||||||
|
"Fehler: " + e.getMessage(), 0, currentStatus.startedAt());
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
log.error("Massenimport fehlgeschlagen", e);
|
log.error("Massenimport fehlgeschlagen", e);
|
||||||
currentStatus = new ImportStatus(State.FAILED, "Fehler: " + e.getMessage(), 0, currentStatus.startedAt());
|
currentStatus = new ImportStatus(State.FAILED, "IMPORT_FAILED_INTERNAL",
|
||||||
|
"Fehler: " + e.getMessage(), 0, currentStatus.startedAt());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static class NoSpreadsheetException extends RuntimeException {
|
||||||
|
NoSpreadsheetException(String message) { super(message); }
|
||||||
|
}
|
||||||
|
|
||||||
private File findSpreadsheetFile() throws IOException {
|
private File findSpreadsheetFile() throws IOException {
|
||||||
try (Stream<Path> files = Files.list(Paths.get(importDir))) {
|
try (Stream<Path> files = Files.list(Paths.get(importDir))) {
|
||||||
return files
|
return files
|
||||||
@@ -138,7 +147,7 @@ public class MassImportService {
|
|||||||
return name.endsWith(".ods") || name.endsWith(".xlsx") || name.endsWith(".xls");
|
return name.endsWith(".ods") || name.endsWith(".xlsx") || name.endsWith(".xls");
|
||||||
})
|
})
|
||||||
.findFirst()
|
.findFirst()
|
||||||
.orElseThrow(() -> new RuntimeException(
|
.orElseThrow(() -> new NoSpreadsheetException(
|
||||||
"Keine Tabellendatei (.ods/.xlsx/.xls) in " + importDir + " gefunden!"))
|
"Keine Tabellendatei (.ods/.xlsx/.xls) in " + importDir + " gefunden!"))
|
||||||
.toFile();
|
.toFile();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -70,14 +70,20 @@ class MassImportServiceTest {
|
|||||||
assertThat(service.getStatus().state()).isEqualTo(MassImportService.State.IDLE);
|
assertThat(service.getStatus().state()).isEqualTo(MassImportService.State.IDLE);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void getStatus_hasStatusCode_IMPORT_IDLE_byDefault() {
|
||||||
|
assertThat(service.getStatus().statusCode()).isEqualTo("IMPORT_IDLE");
|
||||||
|
}
|
||||||
|
|
||||||
// ─── runImportAsync ───────────────────────────────────────────────────────
|
// ─── runImportAsync ───────────────────────────────────────────────────────
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void runImportAsync_setsFailedStatus_whenImportDirectoryDoesNotExist() {
|
void runImportAsync_setsFailedStatus_whenImportDirectoryDoesNotExist() {
|
||||||
// /import directory doesn't exist in test environment → findSpreadsheetFile throws
|
// /import directory doesn't exist in test environment → IOException → IMPORT_FAILED_INTERNAL
|
||||||
service.runImportAsync();
|
service.runImportAsync();
|
||||||
|
|
||||||
assertThat(service.getStatus().state()).isEqualTo(MassImportService.State.FAILED);
|
assertThat(service.getStatus().state()).isEqualTo(MassImportService.State.FAILED);
|
||||||
|
assertThat(service.getStatus().statusCode()).isEqualTo("IMPORT_FAILED_INTERNAL");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -93,10 +99,19 @@ class MassImportServiceTest {
|
|||||||
assertThat(service.getStatus().message()).contains(tempDir.toString());
|
assertThat(service.getStatus().message()).contains(tempDir.toString());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void runImportAsync_setsStatusCode_IMPORT_FAILED_NO_SPREADSHEET_whenDirIsEmpty(@TempDir Path tempDir) {
|
||||||
|
ReflectionTestUtils.setField(service, "importDir", tempDir.toString());
|
||||||
|
|
||||||
|
service.runImportAsync();
|
||||||
|
|
||||||
|
assertThat(service.getStatus().statusCode()).isEqualTo("IMPORT_FAILED_NO_SPREADSHEET");
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void runImportAsync_throwsConflict_whenAlreadyRunning() {
|
void runImportAsync_throwsConflict_whenAlreadyRunning() {
|
||||||
MassImportService.ImportStatus running = new MassImportService.ImportStatus(
|
MassImportService.ImportStatus running = new MassImportService.ImportStatus(
|
||||||
MassImportService.State.RUNNING, "Running...", 0, LocalDateTime.now());
|
MassImportService.State.RUNNING, "IMPORT_RUNNING", "Running...", 0, LocalDateTime.now());
|
||||||
ReflectionTestUtils.setField(service, "currentStatus", running);
|
ReflectionTestUtils.setField(service, "currentStatus", running);
|
||||||
|
|
||||||
assertThatThrownBy(() -> service.runImportAsync())
|
assertThatThrownBy(() -> service.runImportAsync())
|
||||||
|
|||||||
@@ -40,6 +40,22 @@ class AdminControllerTest {
|
|||||||
@MockitoBean ThumbnailBackfillService thumbnailBackfillService;
|
@MockitoBean ThumbnailBackfillService thumbnailBackfillService;
|
||||||
@MockitoBean CustomUserDetailsService customUserDetailsService;
|
@MockitoBean CustomUserDetailsService customUserDetailsService;
|
||||||
|
|
||||||
|
// ─── GET /api/admin/import-status ─────────────────────────────────────────
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@WithMockUser(authorities = "ADMIN")
|
||||||
|
void importStatus_returns200_withStatusCode_whenAdmin() throws Exception {
|
||||||
|
MassImportService.ImportStatus status = new MassImportService.ImportStatus(
|
||||||
|
MassImportService.State.IDLE, "IMPORT_IDLE", "Kein Import gestartet.", 0, null);
|
||||||
|
when(massImportService.getStatus()).thenReturn(status);
|
||||||
|
|
||||||
|
mockMvc.perform(get("/api/admin/import-status"))
|
||||||
|
.andExpect(status().isOk())
|
||||||
|
.andExpect(jsonPath("$.state").value("IDLE"))
|
||||||
|
.andExpect(jsonPath("$.statusCode").value("IMPORT_IDLE"))
|
||||||
|
.andExpect(jsonPath("$.processed").value(0));
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void backfillVersions_returns401_whenUnauthenticated() throws Exception {
|
void backfillVersions_returns401_whenUnauthenticated() throws Exception {
|
||||||
mockMvc.perform(post("/api/admin/backfill-versions"))
|
mockMvc.perform(post("/api/admin/backfill-versions"))
|
||||||
|
|||||||
@@ -345,8 +345,11 @@
|
|||||||
"admin_system_import_btn_retry": "Erneut starten",
|
"admin_system_import_btn_retry": "Erneut starten",
|
||||||
"admin_system_import_status_idle": "Kein Import gestartet.",
|
"admin_system_import_status_idle": "Kein Import gestartet.",
|
||||||
"admin_system_import_status_running": "Import läuft…",
|
"admin_system_import_status_running": "Import läuft…",
|
||||||
"admin_system_import_status_done": "Import abgeschlossen – {count} Dokumente verarbeitet.",
|
"admin_system_import_status_done": "Import abgeschlossen",
|
||||||
"admin_system_import_status_failed": "Fehler: {message}",
|
"admin_system_import_status_done_label": "Dokumente verarbeitet",
|
||||||
|
"admin_system_import_status_failed": "Import fehlgeschlagen",
|
||||||
|
"admin_system_import_failed_no_spreadsheet": "Keine Tabellendatei gefunden.",
|
||||||
|
"admin_system_import_failed_internal": "Interner Fehler beim Import.",
|
||||||
"admin_system_thumbnails_heading": "Thumbnails erzeugen",
|
"admin_system_thumbnails_heading": "Thumbnails erzeugen",
|
||||||
"admin_system_thumbnails_description": "Erzeugt Vorschaubilder für Dokumente ohne Thumbnail (z. B. nach dem Massenimport).",
|
"admin_system_thumbnails_description": "Erzeugt Vorschaubilder für Dokumente ohne Thumbnail (z. B. nach dem Massenimport).",
|
||||||
"admin_system_thumbnails_btn_start": "Thumbnails erzeugen",
|
"admin_system_thumbnails_btn_start": "Thumbnails erzeugen",
|
||||||
|
|||||||
@@ -345,8 +345,11 @@
|
|||||||
"admin_system_import_btn_retry": "Start again",
|
"admin_system_import_btn_retry": "Start again",
|
||||||
"admin_system_import_status_idle": "No import started.",
|
"admin_system_import_status_idle": "No import started.",
|
||||||
"admin_system_import_status_running": "Import running…",
|
"admin_system_import_status_running": "Import running…",
|
||||||
"admin_system_import_status_done": "Import complete – {count} documents processed.",
|
"admin_system_import_status_done": "Import complete",
|
||||||
"admin_system_import_status_failed": "Error: {message}",
|
"admin_system_import_status_done_label": "Documents processed",
|
||||||
|
"admin_system_import_status_failed": "Import failed",
|
||||||
|
"admin_system_import_failed_no_spreadsheet": "No spreadsheet file found.",
|
||||||
|
"admin_system_import_failed_internal": "Import failed due to an internal error.",
|
||||||
"admin_system_thumbnails_heading": "Generate thumbnails",
|
"admin_system_thumbnails_heading": "Generate thumbnails",
|
||||||
"admin_system_thumbnails_description": "Generates preview images for documents without a thumbnail (e.g. after the mass import).",
|
"admin_system_thumbnails_description": "Generates preview images for documents without a thumbnail (e.g. after the mass import).",
|
||||||
"admin_system_thumbnails_btn_start": "Generate thumbnails",
|
"admin_system_thumbnails_btn_start": "Generate thumbnails",
|
||||||
|
|||||||
@@ -345,8 +345,11 @@
|
|||||||
"admin_system_import_btn_retry": "Iniciar de nuevo",
|
"admin_system_import_btn_retry": "Iniciar de nuevo",
|
||||||
"admin_system_import_status_idle": "No hay importación iniciada.",
|
"admin_system_import_status_idle": "No hay importación iniciada.",
|
||||||
"admin_system_import_status_running": "Importación en curso…",
|
"admin_system_import_status_running": "Importación en curso…",
|
||||||
"admin_system_import_status_done": "Importación completada – {count} documentos procesados.",
|
"admin_system_import_status_done": "Importación completada",
|
||||||
"admin_system_import_status_failed": "Error: {message}",
|
"admin_system_import_status_done_label": "Documentos procesados",
|
||||||
|
"admin_system_import_status_failed": "Importación fallida",
|
||||||
|
"admin_system_import_failed_no_spreadsheet": "No se encontró ninguna hoja de cálculo.",
|
||||||
|
"admin_system_import_failed_internal": "Error interno durante la importación.",
|
||||||
"admin_system_thumbnails_heading": "Generar miniaturas",
|
"admin_system_thumbnails_heading": "Generar miniaturas",
|
||||||
"admin_system_thumbnails_description": "Genera imágenes de vista previa para documentos sin miniatura (p. ej. tras la importación masiva).",
|
"admin_system_thumbnails_description": "Genera imágenes de vista previa para documentos sin miniatura (p. ej. tras la importación masiva).",
|
||||||
"admin_system_thumbnails_btn_start": "Generar miniaturas",
|
"admin_system_thumbnails_btn_start": "Generar miniaturas",
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { onDestroy } from 'svelte';
|
import { onDestroy } from 'svelte';
|
||||||
import { m } from '$lib/paraglide/messages.js';
|
import { m } from '$lib/paraglide/messages.js';
|
||||||
|
import ImportStatusCard from './ImportStatusCard.svelte';
|
||||||
|
|
||||||
let backfillResult: number | null = $state(null);
|
let backfillResult: number | null = $state(null);
|
||||||
let backfillLoading = $state(false);
|
let backfillLoading = $state(false);
|
||||||
@@ -9,6 +10,7 @@ let backfillHashesLoading = $state(false);
|
|||||||
|
|
||||||
type ImportStatus = {
|
type ImportStatus = {
|
||||||
state: 'IDLE' | 'RUNNING' | 'DONE' | 'FAILED';
|
state: 'IDLE' | 'RUNNING' | 'DONE' | 'FAILED';
|
||||||
|
statusCode: string;
|
||||||
message: string;
|
message: string;
|
||||||
processed: number;
|
processed: number;
|
||||||
startedAt: string | null;
|
startedAt: string | null;
|
||||||
@@ -177,47 +179,7 @@ async function backfillFileHashes() {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Mass import -->
|
<!-- Mass import -->
|
||||||
<div class="rounded-sm border border-line bg-surface p-6 shadow-sm">
|
<ImportStatusCard importStatus={importStatus} ontrigger={triggerImport} />
|
||||||
<h2 class="mb-1 font-sans text-sm font-bold text-ink">{m.admin_system_import_heading()}</h2>
|
|
||||||
<p class="mb-4 text-sm text-ink-2">{m.admin_system_import_description()}</p>
|
|
||||||
|
|
||||||
{#if importStatus?.state === 'RUNNING'}
|
|
||||||
<p class="text-sm text-ink-2">{m.admin_system_import_status_running()}</p>
|
|
||||||
{:else if importStatus?.state === 'DONE'}
|
|
||||||
<p class="mb-4 rounded-sm border border-green-200 bg-green-50 p-3 text-sm text-green-700">
|
|
||||||
{m.admin_system_import_status_done({ count: importStatus.processed })}
|
|
||||||
</p>
|
|
||||||
<button
|
|
||||||
data-import-trigger
|
|
||||||
onclick={triggerImport}
|
|
||||||
class="rounded-sm bg-primary px-5 py-2 font-sans text-xs font-bold tracking-widest text-primary-fg uppercase transition-opacity hover:opacity-80"
|
|
||||||
>
|
|
||||||
{m.admin_system_import_btn_retry()}
|
|
||||||
</button>
|
|
||||||
{:else if importStatus?.state === 'FAILED'}
|
|
||||||
<p class="mb-4 rounded-sm border border-red-200 bg-red-50 p-3 text-sm text-red-700">
|
|
||||||
{m.admin_system_import_status_failed({ message: importStatus.message })}
|
|
||||||
</p>
|
|
||||||
<button
|
|
||||||
data-import-trigger
|
|
||||||
onclick={triggerImport}
|
|
||||||
class="rounded-sm bg-primary px-5 py-2 font-sans text-xs font-bold tracking-widest text-primary-fg uppercase transition-opacity hover:opacity-80"
|
|
||||||
>
|
|
||||||
{m.admin_system_import_btn_retry()}
|
|
||||||
</button>
|
|
||||||
{:else}
|
|
||||||
{#if importStatus !== null}
|
|
||||||
<p class="mb-4 text-sm text-ink-2">{m.admin_system_import_status_idle()}</p>
|
|
||||||
{/if}
|
|
||||||
<button
|
|
||||||
data-import-trigger
|
|
||||||
onclick={triggerImport}
|
|
||||||
class="rounded-sm bg-primary px-5 py-2 font-sans text-xs font-bold tracking-widest text-primary-fg uppercase transition-opacity hover:opacity-80"
|
|
||||||
>
|
|
||||||
{m.admin_system_import_btn_start()}
|
|
||||||
</button>
|
|
||||||
{/if}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Thumbnail backfill -->
|
<!-- Thumbnail backfill -->
|
||||||
<div class="rounded-sm border border-line bg-surface p-6 shadow-sm">
|
<div class="rounded-sm border border-line bg-surface p-6 shadow-sm">
|
||||||
|
|||||||
80
frontend/src/routes/admin/system/ImportStatusCard.svelte
Normal file
80
frontend/src/routes/admin/system/ImportStatusCard.svelte
Normal file
@@ -0,0 +1,80 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import { m } from '$lib/paraglide/messages.js';
|
||||||
|
|
||||||
|
type ImportStatus = {
|
||||||
|
state: 'IDLE' | 'RUNNING' | 'DONE' | 'FAILED';
|
||||||
|
statusCode: string;
|
||||||
|
message: string;
|
||||||
|
processed: number;
|
||||||
|
startedAt: string | null;
|
||||||
|
};
|
||||||
|
|
||||||
|
let {
|
||||||
|
importStatus,
|
||||||
|
ontrigger
|
||||||
|
}: {
|
||||||
|
importStatus: ImportStatus | null;
|
||||||
|
ontrigger: () => void;
|
||||||
|
} = $props();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="rounded-sm border border-line bg-surface p-6 shadow-sm">
|
||||||
|
<h2 class="mb-1 font-sans text-sm font-bold text-ink">{m.admin_system_import_heading()}</h2>
|
||||||
|
<p class="mb-4 text-sm text-ink-2">{m.admin_system_import_description()}</p>
|
||||||
|
|
||||||
|
{#if importStatus?.state === 'RUNNING'}
|
||||||
|
<div class="mb-4 flex items-center gap-3">
|
||||||
|
<span
|
||||||
|
data-testid="spinner"
|
||||||
|
role="status"
|
||||||
|
aria-label={m.admin_system_import_status_running()}
|
||||||
|
class="inline-block h-5 w-5 animate-spin rounded-full border-2 border-ink-3 border-t-brand-mint"
|
||||||
|
></span>
|
||||||
|
<div>
|
||||||
|
<p class="text-base font-bold text-ink">{importStatus.processed}</p>
|
||||||
|
<p class="text-xs font-bold tracking-widest text-ink-3 uppercase">
|
||||||
|
{m.admin_system_import_status_running()}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{:else if importStatus?.state === 'DONE'}
|
||||||
|
<div class="mb-4 rounded-sm border border-green-200 bg-green-50 p-4 text-green-700">
|
||||||
|
<p class="text-base font-bold">{importStatus.processed}</p>
|
||||||
|
<p class="text-xs font-bold tracking-widest text-green-600 uppercase">
|
||||||
|
{m.admin_system_import_status_done_label()}
|
||||||
|
</p>
|
||||||
|
<p class="mt-1 text-xs text-green-600">{m.admin_system_import_status_done()}</p>
|
||||||
|
</div>
|
||||||
|
<button
|
||||||
|
data-import-trigger
|
||||||
|
onclick={ontrigger}
|
||||||
|
class="rounded-sm bg-primary px-5 py-2 font-sans text-xs font-bold tracking-widest text-primary-fg uppercase transition-opacity hover:opacity-80"
|
||||||
|
>
|
||||||
|
{m.admin_system_import_btn_retry()}
|
||||||
|
</button>
|
||||||
|
{:else if importStatus?.state === 'FAILED'}
|
||||||
|
<p class="mb-4 rounded-sm border border-red-200 bg-red-50 p-3 text-sm text-red-700">
|
||||||
|
{importStatus.statusCode === 'IMPORT_FAILED_NO_SPREADSHEET'
|
||||||
|
? m.admin_system_import_failed_no_spreadsheet()
|
||||||
|
: m.admin_system_import_failed_internal()}
|
||||||
|
</p>
|
||||||
|
<button
|
||||||
|
data-import-trigger
|
||||||
|
onclick={ontrigger}
|
||||||
|
class="rounded-sm bg-primary px-5 py-2 font-sans text-xs font-bold tracking-widest text-primary-fg uppercase transition-opacity hover:opacity-80"
|
||||||
|
>
|
||||||
|
{m.admin_system_import_btn_retry()}
|
||||||
|
</button>
|
||||||
|
{:else}
|
||||||
|
{#if importStatus !== null}
|
||||||
|
<p class="mb-4 text-sm text-ink-2">{m.admin_system_import_status_idle()}</p>
|
||||||
|
{/if}
|
||||||
|
<button
|
||||||
|
data-import-trigger
|
||||||
|
onclick={ontrigger}
|
||||||
|
class="rounded-sm bg-primary px-5 py-2 font-sans text-xs font-bold tracking-widest text-primary-fg uppercase transition-opacity hover:opacity-80"
|
||||||
|
>
|
||||||
|
{m.admin_system_import_btn_start()}
|
||||||
|
</button>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
@@ -0,0 +1,71 @@
|
|||||||
|
import { describe, it } from 'vitest';
|
||||||
|
import { render } from 'vitest-browser-svelte';
|
||||||
|
import { expect } from '@vitest/browser/context';
|
||||||
|
import ImportStatusCard from './ImportStatusCard.svelte';
|
||||||
|
|
||||||
|
type ImportStatus = {
|
||||||
|
state: 'IDLE' | 'RUNNING' | 'DONE' | 'FAILED';
|
||||||
|
statusCode: string;
|
||||||
|
message: string;
|
||||||
|
processed: number;
|
||||||
|
startedAt: string | null;
|
||||||
|
};
|
||||||
|
|
||||||
|
const makeStatus = (overrides: Partial<ImportStatus> = {}): ImportStatus => ({
|
||||||
|
state: 'IDLE',
|
||||||
|
statusCode: 'IMPORT_IDLE',
|
||||||
|
message: '',
|
||||||
|
processed: 0,
|
||||||
|
startedAt: null,
|
||||||
|
...overrides
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('ImportStatusCard', () => {
|
||||||
|
it('shows spinner while state is RUNNING', async () => {
|
||||||
|
const { getByTestId } = render(ImportStatusCard, {
|
||||||
|
props: {
|
||||||
|
importStatus: makeStatus({ state: 'RUNNING', statusCode: 'IMPORT_RUNNING', processed: 3 }),
|
||||||
|
ontrigger: () => {}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
await expect.element(getByTestId('spinner')).toBeVisible();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('shows processed count at text-base while RUNNING', async () => {
|
||||||
|
const { getByText } = render(ImportStatusCard, {
|
||||||
|
props: {
|
||||||
|
importStatus: makeStatus({ state: 'RUNNING', statusCode: 'IMPORT_RUNNING', processed: 7 }),
|
||||||
|
ontrigger: () => {}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
await expect.element(getByText('7')).toBeVisible();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('shows processed count while DONE', async () => {
|
||||||
|
const { getByText } = render(ImportStatusCard, {
|
||||||
|
props: {
|
||||||
|
importStatus: makeStatus({ state: 'DONE', statusCode: 'IMPORT_DONE', processed: 42 }),
|
||||||
|
ontrigger: () => {}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
await expect.element(getByText('42')).toBeVisible();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('shows no-spreadsheet message when statusCode is IMPORT_FAILED_NO_SPREADSHEET', async () => {
|
||||||
|
const { getByText } = render(ImportStatusCard, {
|
||||||
|
props: {
|
||||||
|
importStatus: makeStatus({
|
||||||
|
state: 'FAILED',
|
||||||
|
statusCode: 'IMPORT_FAILED_NO_SPREADSHEET',
|
||||||
|
message: 'Keine Tabellendatei...'
|
||||||
|
}),
|
||||||
|
ontrigger: () => {}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
await expect.element(getByText('No spreadsheet file found.')).toBeVisible();
|
||||||
|
});
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user