package storage import ( "context" "database/sql" "errors" "path/filepath" "reflect" "testing" "time" "provenance/internal/domain" ) func TestSnapshotAndTags(t *testing.T) { ctx := context.Background() s, err := Open(filepath.Join(t.TempDir(), "app.db")) if err != nil { t.Fatal(err) } defer s.Close() if err := s.Join(ctx, "A"); err != nil { t.Fatal(err) } vouches := []domain.Vouch{{Author: "A", Subject: "B", Score: 2}, {Author: "A", Subject: "B", Tag: "Work", Score: 0}} if err := s.Replace(ctx, "A", vouches, time.Now()); err != nil { t.Fatal(err) } graph, err := s.Graph(ctx, "Work", domain.Fallback, 10) if err != nil { t.Fatal(err) } if len(graph) != 2 { t.Fatalf("fallback graph = %+v", graph) } tags, err := s.Tags(ctx, "W", 10) if err != nil { t.Fatal(err) } if !reflect.DeepEqual(tags, []string{"Work"}) { t.Fatalf("tags = %v", tags) } tags, err = s.Tags(ctx, "w", 10) if err != nil || len(tags) != 0 { t.Fatalf("case-sensitive tags = %v, %v", tags, err) } if err := s.Replace(ctx, "A", []domain.Vouch{{Author: "C", Subject: "B", Score: 2}}, time.Now()); err == nil { t.Fatal("accepted forged author") } graph, err = s.Graph(ctx, "Work", domain.Fallback, 10) if err != nil || len(graph) != 2 { t.Fatal("failed snapshot damaged previous records") } if err := s.Replace(ctx, "A", nil, time.Now()); err != nil { t.Fatal(err) } tags, err = s.Tags(ctx, "", 10) if err != nil || len(tags) != 0 { t.Fatal("removed records remain in tag index") } } func TestThresholdAndSecrets(t *testing.T) { path := filepath.Join(t.TempDir(), "app.db") ctx := context.Background() s, err := Open(path) if err != nil { t.Fatal(err) } if err := s.Join(ctx, "A"); err != nil { t.Fatal(err) } if err := s.SetThreshold(ctx, "A", 1.2); err != nil { t.Fatal(err) } if err := s.SetThreshold(ctx, "missing", 0); !errors.Is(err, sql.ErrNoRows) { t.Fatalf("got %v", err) } if err := s.Add(ctx, "state", "one", []byte("secret"), time.Now().Add(time.Hour)); err != nil { t.Fatal(err) } if err := s.Add(ctx, "state", "one", nil, time.Now().Add(time.Hour)); err == nil { t.Fatal("accepted duplicate state") } if _, err := s.Consume(ctx, "state", "one"); err != nil { t.Fatal(err) } if _, err := s.Consume(ctx, "state", "one"); !errors.Is(err, sql.ErrNoRows) { t.Fatal("state replay succeeded") } if err := s.Put(ctx, "state", "expired", []byte("secret"), time.Now().Add(-time.Hour)); err != nil { t.Fatal(err) } if _, err := s.Get(ctx, "state", "expired"); !errors.Is(err, sql.ErrNoRows) { t.Fatal("expired state accepted") } s.Close() s, err = Open(path) if err != nil { t.Fatal(err) } defer s.Close() p, err := s.Participant(ctx, "A") if err != nil || p.Threshold != 1.2 { t.Fatalf("restart lost threshold: %+v, %v", p, err) } }