feat: Standbild-Upload bei der Pre-Publish-Prüfung
CLAUDE.md beschreibt die Prüfung seit dem ersten Commit als "Caption, Standbild und Vertragslage rein" — bisher wurde nur die Caption verarbeitet, das asset-Schema aus Migration 0001 blieb ungenutzt. - internal/store/asset.go: CreateAsset/GetLatestAssetForSubmission. Migration 0005 macht asset append-only (Trigger fehlte seit 0001, weil bis jetzt nichts hineinschrieb) — ein hochgeladenes Beweisstück wird nicht nachträglich ausgetauscht, aus demselben Grund wie bei extraction/finding/evidence_package. - handleCheck liest ein optionales "standbild"-Formularfeld (Bild- Upload, max. 8 MiB, Content-Type muss image/* sein), validiert es VOR dem Anlegen der Submission (ein ungültiger Upload hinterlässt so keine leere Beitrags-Zeile), speichert es danach unter ASSET_DIR und legt die Asset-Zeile an. - handleArchive bindet den Asset-Hash (falls vorhanden) in den Metadaten-Hash und ins PDF-Dossier ein (dossier.Data.AssetHash war bereits vorbereitet, wurde aber nie befüllt). - index.html: Formular auf multipart/form-data umgestellt (hx-encoding + enctype), neues optionales Dateifeld. handleCheck bleibt abwärtskompatibel zu urlencoded-Requests (ParseMultipartForm liefert ErrNotMultipart, das wird wie "kein Bild hochgeladen" behandelt, nicht wie ein Fehler). - ASSET_DIR neue Konfigurationsvariable (Default "assets", wie DOSSIER_DIR relativ zu WorkingDirectory=/var/lib/deklarix — kein postinst-Healing nötig, anders als bei RULES_DIR, dessen Default nicht zum installierten Pfad passt). Volle Testsuite inkl. echter Postgres-Tests grün; End-to-End gegen einen laufenden Server verifiziert (Upload, Hash in DB, Hash im erzeugten PDF via pdftotext, Ablehnung bei falschem Dateityp).
This commit is contained in:
99
internal/store/asset_test.go
Normal file
99
internal/store/asset_test.go
Normal file
@@ -0,0 +1,99 @@
|
||||
package store_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"testing"
|
||||
|
||||
"github.com/netcell-it/deklarix/internal/store"
|
||||
)
|
||||
|
||||
func TestAssetCreateAndGetLatest(t *testing.T) {
|
||||
s := openTestStore(t)
|
||||
ctx := context.Background()
|
||||
accID := testAccountID(t, s)
|
||||
sub, err := s.CreateSubmission(ctx, accID, "instagram", "feed", "...")
|
||||
if err != nil {
|
||||
t.Fatalf("CreateSubmission: %v", err)
|
||||
}
|
||||
|
||||
a, err := s.CreateAsset(ctx, sub.ID, "image", "/var/lib/deklarix/assets/abc.jpg", "deadbeef")
|
||||
if err != nil {
|
||||
t.Fatalf("CreateAsset: %v", err)
|
||||
}
|
||||
if a.SubmissionID != sub.ID || a.Kind != "image" {
|
||||
t.Fatalf("CreateAsset = %+v, unerwartete Werte", a)
|
||||
}
|
||||
|
||||
got, err := s.GetLatestAssetForSubmission(ctx, sub.ID)
|
||||
if err != nil {
|
||||
t.Fatalf("GetLatestAssetForSubmission: %v", err)
|
||||
}
|
||||
if got.ID != a.ID || got.SHA256 != "deadbeef" {
|
||||
t.Fatalf("GetLatestAssetForSubmission = %+v, want %+v", got, a)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetLatestAssetForSubmissionNotFoundWhenNoneUploaded(t *testing.T) {
|
||||
s := openTestStore(t)
|
||||
ctx := context.Background()
|
||||
accID := testAccountID(t, s)
|
||||
sub, err := s.CreateSubmission(ctx, accID, "instagram", "feed", "...")
|
||||
if err != nil {
|
||||
t.Fatalf("CreateSubmission: %v", err)
|
||||
}
|
||||
|
||||
_, err = s.GetLatestAssetForSubmission(ctx, sub.ID)
|
||||
if !errors.Is(err, store.ErrNotFound) {
|
||||
t.Fatalf("err = %v, want store.ErrNotFound", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetLatestAssetForSubmissionReturnsNewestWhenMultiple(t *testing.T) {
|
||||
s := openTestStore(t)
|
||||
ctx := context.Background()
|
||||
accID := testAccountID(t, s)
|
||||
sub, err := s.CreateSubmission(ctx, accID, "instagram", "feed", "...")
|
||||
if err != nil {
|
||||
t.Fatalf("CreateSubmission: %v", err)
|
||||
}
|
||||
|
||||
if _, err := s.CreateAsset(ctx, sub.ID, "image", "/tmp/erstes.jpg", "erstehash"); err != nil {
|
||||
t.Fatalf("CreateAsset (1): %v", err)
|
||||
}
|
||||
second, err := s.CreateAsset(ctx, sub.ID, "image", "/tmp/zweites.jpg", "zweitehash")
|
||||
if err != nil {
|
||||
t.Fatalf("CreateAsset (2): %v", err)
|
||||
}
|
||||
|
||||
got, err := s.GetLatestAssetForSubmission(ctx, sub.ID)
|
||||
if err != nil {
|
||||
t.Fatalf("GetLatestAssetForSubmission: %v", err)
|
||||
}
|
||||
if got.ID != second.ID {
|
||||
t.Fatalf("expected the newest asset, got %+v", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAssetIsAppendOnly(t *testing.T) {
|
||||
s := openTestStore(t)
|
||||
ctx := context.Background()
|
||||
accID := testAccountID(t, s)
|
||||
sub, err := s.CreateSubmission(ctx, accID, "instagram", "feed", "...")
|
||||
if err != nil {
|
||||
t.Fatalf("CreateSubmission: %v", err)
|
||||
}
|
||||
a, err := s.CreateAsset(ctx, sub.ID, "image", "/tmp/x.jpg", "hash")
|
||||
if err != nil {
|
||||
t.Fatalf("CreateAsset: %v", err)
|
||||
}
|
||||
|
||||
_, err = s.Pool.Exec(ctx, `UPDATE asset SET sha256 = 'geaendert' WHERE id = $1`, a.ID)
|
||||
if err == nil {
|
||||
t.Fatal("expected UPDATE on asset to be rejected by the append-only trigger")
|
||||
}
|
||||
_, err = s.Pool.Exec(ctx, `DELETE FROM asset WHERE id = $1`, a.ID)
|
||||
if err == nil {
|
||||
t.Fatal("expected DELETE on asset to be rejected by the append-only trigger")
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user