Files
Kazier/backend/repository/operations_test.go
T
2026-09-17 07:59:21 +02:00

94 lines
2.9 KiB
Go

package repository
import (
"encoding/json"
"errors"
"testing"
"github.com/kazier/backend/dbtest"
)
func newTestOperations(t *testing.T) (*Operations, *Devices) {
t.Helper()
conn := dbtest.OpenTestDatabase(t, repositoryTestURL)
return &Operations{DB: conn}, &Devices{DB: conn}
}
func TestOperationsRecordAndApplied(t *testing.T) {
o, devices := newTestOperations(t)
deviceID := NewID()
if err := devices.Upsert(deviceID); err != nil {
t.Fatalf("register device: %v", err)
}
opID := NewID()
applied, err := o.Applied(deviceID, opID)
if err != nil || applied {
t.Fatalf("avant record : applied=%v err=%v", applied, err)
}
if err := o.Record(deviceID, opID, "create_resource", "resource", nil, NewID(), []byte(`{"name":"x"}`)); err != nil {
t.Fatalf("record: %v", err)
}
applied, err = o.Applied(deviceID, opID)
if err != nil || !applied {
t.Fatalf("après record : applied=%v err=%v", applied, err)
}
// Rejeu → ON CONFLICT DO NOTHING, aucune erreur.
if err := o.Record(deviceID, opID, "create_resource", "resource", nil, NewID(), []byte(`null`)); err != nil {
t.Fatalf("rejeu: %v", err)
}
if applied, _ := o.Applied(deviceID, opID); !applied {
t.Error("rejeu : l'op doit rester appliquée")
}
// Idempotence par (device_id, operation_id) : un autre device peut réutiliser
// le même operation_id sans conflit (les deux rows sont distinctes).
device02 := NewID()
if err := devices.Upsert(device02); err != nil {
t.Fatalf("register device 2: %v", err)
}
if err := o.Record(device02, opID, "create_resource", "resource", nil, NewID(), []byte(`{}`)); err != nil {
t.Errorf("même op_id sur un autre device: %v", err)
}
}
func TestOperationsRecordRejectsInvalidOperationID(t *testing.T) {
o, devices := newTestOperations(t)
if err := devices.Upsert(NewID()); err != nil {
t.Fatalf("register device: %v", err)
}
err := o.Record(NewID(), "UPPERCASENOT32", "create_resource", "resource", nil, NewID(), []byte(`{}`))
if !errors.Is(err, ErrInvalidOperationID) {
t.Errorf("operation_id invalide : attendu ErrInvalidOperationID, got %v", err)
}
}
func TestOperationsRecordNormalizesInvalidPayload(t *testing.T) {
o, devices := newTestOperations(t)
deviceID := NewID()
if err := devices.Upsert(deviceID); err != nil {
t.Fatalf("register device: %v", err)
}
opID := NewID()
// Un payload non-JSON ne doit pas faire échouer la trace (jsonBytes → {}).
if err := o.Record(deviceID, opID, "create_resource", "resource", nil, NewID(), []byte(`not-json`)); err != nil {
t.Fatalf("record: %v", err)
}
var stored string
if err := o.DB.QueryRow(`SELECT payload::text FROM operations WHERE device_id = $1 AND operation_id = $2`, deviceID, opID).Scan(&stored); err != nil {
t.Fatalf("read payload: %v", err)
}
var parsed any
if err := json.Unmarshal([]byte(stored), &parsed); err != nil {
t.Fatalf("payload stocké invalide: %q", stored)
}
if stored != "{}" {
t.Errorf("payload normalisé attendu {}, got %q", stored)
}
}