Files
Kazier/backend/service/ocr_test.go
T
2026-09-16 14:47:10 +02:00

189 lines
5.9 KiB
Go

package service
import (
"context"
"errors"
"os"
"path/filepath"
"strings"
"testing"
"time"
"github.com/vaultdrop/backend/ocr"
"github.com/vaultdrop/backend/repository"
)
// stubEngine again (package service) — même contrat que handlers/ocr_test.
type stubEngine struct {
text string
err error
}
func (s stubEngine) ExtractText(_ context.Context, _ string, _ string) (string, error) {
if s.err != nil {
return "", s.err
}
return s.text, nil
}
// newTestOcr builds an Ocr over a fresh DB, starts the bounded worker (1
// worker) and returns it with the ids.
func newTestOcr(t *testing.T, uploadDir string, engine ocr.Engine) (*Ocr, string, string, string) {
t.Helper()
s := newServiceStore(t)
userID := mustCreateUser(t, s.Repository, "ocr-failed")
deviceID := mustRegisterDevice(t, s.Repository, repository.NewID())
o := NewOcr(s.Repository, uploadDir, "fra+eng", engine)
ctx, cancel := context.WithCancel(context.Background())
go o.Run(ctx, 1)
t.Cleanup(cancel)
return o, userID, deviceID, uploadDir
}
// waitTillTerminal poll jusqu'à un statut terminal (done/failed).
func waitTillTerminal(t *testing.T, o *Ocr, deviceID, jobID string) repository.OcrJobRow {
t.Helper()
deadline := time.Now().Add(3 * time.Second)
for time.Now().Before(deadline) {
row, err := o.Repository.OcrJobs.Get(deviceID, jobID)
if err != nil {
t.Fatalf("get job: %v", err)
}
if row.Status == "done" || row.Status == "failed" {
return row
}
time.Sleep(50 * time.Millisecond)
}
t.Fatal("job jamais terminal")
return repository.OcrJobRow{}
}
func insertOcrFile(t *testing.T, o *Ocr, userID, fileID string) {
t.Helper()
if err := o.Repository.Resources.InsertFile(userID, fileID, "scan.png", "", 128, nil, nil); err != nil {
t.Fatalf("insert file: %v", err)
}
}
func TestOcrPhysicalPath(t *testing.T) {
uploadDir := t.TempDir()
userID := "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
fileID := "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"
if err := os.MkdirAll(filepath.Join(uploadDir, userID), 0o755); err != nil {
t.Fatalf("mkdir: %v", err)
}
// Vérifie que l'extension du fichier trouvé est acceptée (glob = ordre
// alphabétique, on ne présume pas du nom choisi).
if err := os.WriteFile(filepath.Join(uploadDir, userID, fileID+".png"), []byte("x"), 0o644); err != nil {
t.Fatalf("write png: %v", err)
}
o := NewOcr(nil, uploadDir, "fra+eng", nil)
found, err := o.physicalPath(userID, fileID)
if err != nil {
t.Fatalf("physicalPath: %v", err)
}
if !strings.HasPrefix(filepath.Base(found), fileID+".") {
t.Errorf("path = %q, attendu préfixe %q", found, fileID+".")
}
if _, err := o.physicalPath(userID, "cccccccccccccccccccccccccccccccc"); !errors.Is(err, repository.ErrNotFound) {
t.Errorf("fichier absent : attendu ErrNotFound, got %v", err)
}
}
func TestOcrJobFailsWhenPhysicalFileMissing(t *testing.T) {
o, userID, deviceID, _ := newTestOcr(t, t.TempDir(), stubEngine{text: "x"})
fileID := repository.NewID()
insertOcrFile(t, o, userID, fileID)
job, err := o.Create(userID, deviceID, fileID)
if err != nil {
t.Fatalf("create: %v", err)
}
row := waitTillTerminal(t, o, deviceID, job.ID)
if row.Status != "failed" || row.Error == nil {
t.Errorf("fichier physique absent : attendu failed avec erreur, got %+v", row)
}
}
func TestOcrJobPersistsTextOnResourceAndGrantsRead(t *testing.T) {
uploadDir := t.TempDir()
o, ownerID, deviceID, _ := newTestOcr(t, uploadDir, stubEngine{text: " extrait du scan "})
granteeID := mustCreateUser(t, o.Repository, "ocr-grantee")
outsiderID := mustCreateUser(t, o.Repository, "ocr-outsider")
mustRegisterDevice(t, o.Repository, shareTestDeviceID)
fileID := repository.NewID()
insertOcrFile(t, o, ownerID, fileID)
if err := os.MkdirAll(filepath.Join(uploadDir, ownerID), 0o755); err != nil {
t.Fatalf("mkdir: %v", err)
}
if err := os.WriteFile(filepath.Join(uploadDir, ownerID, fileID+".png"), []byte("img"), 0o644); err != nil {
t.Fatalf("write: %v", err)
}
job, err := o.Create(ownerID, deviceID, fileID)
if err != nil {
t.Fatalf("create: %v", err)
}
row := waitTillTerminal(t, o, deviceID, job.ID)
if row.Status != "done" {
t.Fatalf("job %s: attendu done", row.Status)
}
// Le texte (trimé) a atterri sur la ressource, lisible par l'owner.
s := NewResources(o.Repository, uploadDir, 100)
owned, err := s.GetFileOcr(ownerID, fileID)
if err != nil {
t.Fatalf("get ocr owner: %v", err)
}
if owned.Text != "extrait du scan" {
t.Errorf("ocr_text = %q, attendu %q", owned.Text, "extrait du scan")
}
// ... et par un grantee viewer+ (docs §5 : partage rend le texte lisible).
if err := o.Repository.Shares.Upsert(fileID, granteeID, "viewer", false, nil, ownerID); err != nil {
t.Fatalf("share: %v", err)
}
shared, err := s.GetFileOcr(granteeID, fileID)
if err != nil {
t.Fatalf("get ocr grantee: %v", err)
}
if shared.Text != "extrait du scan" {
t.Errorf("ocr_text grantee = %q, attendu %q", shared.Text, "extrait du scan")
}
// Un tiers n'y a pas accès.
if _, err := s.GetFileOcr(outsiderID, fileID); !errors.Is(err, repository.ErrNotFound) {
t.Errorf("tiers : attendu ErrNotFound, got %v", err)
}
}
func TestOcrJobFailsOnEngineError(t *testing.T) {
uploadDir := t.TempDir()
o, userID, deviceID, _ := newTestOcr(t, uploadDir, stubEngine{err: errors.New("tesseract boom")})
fileID := repository.NewID()
insertOcrFile(t, o, userID, fileID)
// Le fichier physique doit exister pour atteindre l'engine.
if err := os.MkdirAll(filepath.Join(uploadDir, userID), 0o755); err != nil {
t.Fatalf("mkdir: %v", err)
}
if err := os.WriteFile(filepath.Join(uploadDir, userID, fileID+".png"), []byte("img"), 0o644); err != nil {
t.Fatalf("write: %v", err)
}
job, err := o.Create(userID, deviceID, fileID)
if err != nil {
t.Fatalf("create: %v", err)
}
row := waitTillTerminal(t, o, deviceID, job.ID)
if row.Status != "failed" || row.Error == nil || !strings.Contains(*row.Error, "tesseract boom") {
t.Errorf("erreur engine : attendu failed avec message engine, got %+v", row)
}
}