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).
100 lines
2.8 KiB
Go
100 lines
2.8 KiB
Go
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")
|
|
}
|
|
}
|