Something went wrong. Try again.
An ATProto Powered Reputation Network
Something went wrong. Try again.
7.1 kB · 173 lines
Go
at master
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174package storage
import ( "context" "database/sql" "errors" "path/filepath" "reflect" "testing" "time"
"provenance/internal/domain" "provenance/internal/trust" "provenance/internal/trust/algorithms")
func TestTrustCacheLifecycle(t *testing.T) { ctx := context.Background() path := filepath.Join(t.TempDir(), "cache.db") s, err := Open(path) if err != nil { t.Fatal(err) } t.Cleanup(func() { s.Close() }) const author = "did:plc:a" v := domain.Vouch{Author: author, Subject: "did:plc:b", Tag: "work", Score: 2, Comment: "Reference."} other := domain.Vouch{Author: author, Subject: "did:plc:c", Score: 1} if err := s.Join(ctx, author); err != nil { t.Fatal(err) } if err := s.Replace(ctx, author, []domain.Vouch{v, other}, time.UnixMicro(100)); err != nil { t.Fatal(err) } now := time.UnixMicro(1000) graph, revision, err := s.TrustGraph(ctx, "work", domain.Tagged, 10) if err != nil { t.Fatal(err) } result, err := (algorithms.Optimistic{}).Calculate(ctx, trust.Input{Edges: graph, Source: author, Target: v.Subject, Limits: trust.Limits{States: 10, Edges: 10, Penalty: domain.DefaultPenalty}}) if err != nil { t.Fatal(err) } result.PathScores[1].Comment = v.Comment deps := []trust.Dependency{{Author: author, RecordKey: domain.RecordKey(v.Subject, v.Tag)}} save := func(key string, revision int64) { t.Helper() saved, err := s.SaveTrust(ctx, key, result, deps, revision, now.Add(time.Hour)) if err != nil || !saved { t.Fatalf("save: %v %v", saved, err) } } save("used", revision) save("also-used", revision) if saved, err := s.SaveTrust(ctx, "unrelated", result, []trust.Dependency{{Author: author, RecordKey: domain.RecordKey(other.Subject, other.Tag)}}, revision, now.Add(time.Hour)); err != nil || !saved { t.Fatalf("unrelated save: %v %v", saved, err) } s.Close() s, err = Open(path) if err != nil { t.Fatal(err) } got, err := s.CachedTrust(ctx, "used", now) if err != nil || !reflect.DeepEqual(got, result) { t.Fatalf("persisted path: %+v %v", got, err) } if _, err := s.CachedTrust(ctx, "used", now.Add(time.Hour)); !errors.Is(err, sql.ErrNoRows) { t.Fatalf("expiration boundary: %v", err) } if err := s.Replace(ctx, author, []domain.Vouch{v, other}, time.UnixMicro(150)); err != nil { t.Fatal(err) } if _, err := s.CachedTrust(ctx, "used", now); err != nil { t.Fatalf("unchanged snapshot invalidated cache: %v", err) } newEdge := domain.Vouch{Author: author, Subject: "did:plc:d", Score: 2} if err := s.Apply(ctx, "stream", domain.Change{Kind: domain.RecordChange, DID: author, RecordKey: domain.RecordKey(newEdge.Subject, newEdge.Tag), Vouch: &newEdge, TimeUS: 200}); err != nil { t.Fatal(err) } if _, err := s.CachedTrust(ctx, "used", now); err != nil { t.Fatalf("unrelated insert invalidated cache: %v", err) } alternate := v alternate.Tag = "" if err := s.Apply(ctx, "stream", domain.Change{Kind: domain.RecordChange, DID: author, RecordKey: domain.RecordKey(alternate.Subject, alternate.Tag), Vouch: &alternate, TimeUS: 220}); err != nil { t.Fatal(err) } foreign := v foreign.Author = "did:plc:x" if err := s.Join(ctx, foreign.Author); err != nil { t.Fatal(err) } if err := s.Apply(ctx, "stream", domain.Change{Kind: domain.RecordChange, DID: foreign.Author, RecordKey: deps[0].RecordKey, Vouch: &foreign, TimeUS: 250}); err != nil { t.Fatal(err) } if _, err := s.CachedTrust(ctx, "used", now); err != nil { t.Fatalf("another author or tag invalidated the path: %v", err) } if err := s.Apply(ctx, "stream", domain.Change{Kind: domain.RecordChange, DID: author, RecordKey: deps[0].RecordKey, Vouch: &foreign, TimeUS: 290}); err == nil { t.Fatal("forged authority accepted") } if _, err := s.CachedTrust(ctx, "used", now); err != nil { t.Fatalf("failed mutation invalidated the cache: %v", err) } if saved, err := s.SaveTrust(ctx, "stale", result, deps, revision, now.Add(time.Hour)); err != nil || saved { t.Fatalf("stale publication: %v %v", saved, err) } v.Score = -1 if err := s.Apply(ctx, "stream", domain.Change{Kind: domain.RecordChange, DID: author, RecordKey: deps[0].RecordKey, Vouch: &v, TimeUS: 300}); err != nil { t.Fatal(err) } for _, key := range []string{"used", "also-used", "stale"} { if _, err := s.CachedTrust(ctx, key, now); !errors.Is(err, sql.ErrNoRows) { t.Fatalf("path update retained %s: %v", key, err) } } if _, err := s.CachedTrust(ctx, "unrelated", now); err != nil { t.Fatalf("path update invalidated unrelated cache: %v", err) } _, revision, err = s.TrustGraph(ctx, "work", domain.Tagged, 10) if err != nil { t.Fatal(err) } save("snapshot-changed", revision) v.Comment = "Changed reference." if err := s.Replace(ctx, author, []domain.Vouch{v, other, newEdge, alternate}, time.UnixMicro(350)); err != nil { t.Fatal(err) } if _, err := s.CachedTrust(ctx, "snapshot-changed", now); !errors.Is(err, sql.ErrNoRows) { t.Fatalf("changed snapshot retained cache: %v", err) } _, revision, err = s.TrustGraph(ctx, "work", domain.Tagged, 10) if err != nil { t.Fatal(err) } save("deleted", revision) if err := s.Apply(ctx, "stream", domain.Change{Kind: domain.RecordChange, DID: author, RecordKey: deps[0].RecordKey, TimeUS: 400}); err != nil { t.Fatal(err) } if _, err := s.CachedTrust(ctx, "deleted", now); !errors.Is(err, sql.ErrNoRows) { t.Fatalf("deleted record retained cache: %v", err) } var count int if err := s.conn.QueryRow("SELECT count(*) FROM trust_dependencies WHERE cache_key != 'unrelated'").Scan(&count); err != nil || count != 0 { t.Fatalf("orphan dependencies: %d %v", count, err) }}
func TestTrustCacheMigration(t *testing.T) { path := filepath.Join(t.TempDir(), "version3.db") conn, err := sql.Open(sqliteDriver, path) if err != nil { t.Fatal(err) } if _, err := conn.Exec("CREATE TABLE participants(did TEXT PRIMARY KEY, threshold REAL NOT NULL DEFAULT 1, synced_at TEXT NOT NULL DEFAULT '', bootstrap_us INTEGER NOT NULL DEFAULT 0, active INTEGER NOT NULL DEFAULT 1, account_us INTEGER NOT NULL DEFAULT 0); CREATE TABLE vouches(author TEXT NOT NULL, subject TEXT NOT NULL, tag TEXT NOT NULL, score INTEGER NOT NULL, created_at TEXT NOT NULL, record_key TEXT NOT NULL, comment TEXT NOT NULL DEFAULT '', PRIMARY KEY(author,subject,tag)); INSERT INTO participants(did) VALUES ('did:plc:a'); INSERT INTO vouches VALUES ('did:plc:a','did:plc:b','',2,'','key','reference'); PRAGMA user_version=3;"); err != nil { t.Fatal(err) } conn.Close() s, err := Open(path) if err != nil { t.Fatal(err) } defer s.Close() graph, _, err := s.TrustGraph(context.Background(), "", domain.Default, 10) if err != nil || len(graph) != 1 || graph[0].Comment != "reference" { t.Fatalf("migration lost data: %+v %v", graph, err) } for _, index := range []string{"trust_cache_expiry", "trust_dependencies_vouch", "vouches_subject_tag"} { var name string if err := s.conn.QueryRow("SELECT name FROM sqlite_master WHERE type = 'index' AND name = ?", index).Scan(&name); err != nil { t.Fatalf("missing index %s: %v", index, err) } }}