Artifact bf9f8c0e9897038c5fe6cd359d193b6cf4a93a128276183ffab61a96fb0f95de:
- File
dst/mock_fossil_test.go
— part of check-in
[1c02c944ec]
at
2026-08-07 17:33:21
on branch trunk
— fix(db): seed hash-policy so new repos name artifacts like fossil init (#225) (#226)
SeedConfig never wrote hash-policy, so naming fell back to deriving from the repo's own artifacts — and an empty repo has none, settling on SHA1 permanently. Seed sha3, which is what fossil init writes.
Clone drops the row the same way it drops project-code: a clone adopts its source's policy, and an absent row derives naming from the artifacts that arrive.
Moves the policy enum to db so the seed and hash.NamingFor read one definition, and updates fixtures that hardcoded SHA1 names to ask the repo how it names artifacts. (user: [email protected], size: 5516) [annotate] [blame] [check-ins using]
package dst import ( "context" "path/filepath" "testing" "github.com/danmestas/go-libfossil/internal/blob" "github.com/danmestas/go-libfossil/internal/hash" "github.com/danmestas/go-libfossil/internal/repo" "github.com/danmestas/go-libfossil/internal/xfer" "github.com/danmestas/go-libfossil/simio" ) func createMockFossil(t *testing.T) *MockFossil { t.Helper() path := filepath.Join(t.TempDir(), "master.fossil") r, err := repo.Create(path, "master", simio.CryptoRand{}, "") if err != nil { t.Fatalf("repo.Create: %v", err) } t.Cleanup(func() { r.Close() }) return NewMockFossil(r) } func TestMockFossilStoreArtifact(t *testing.T) { mf := createMockFossil(t) data := []byte("hello from mock fossil") uuid, err := mf.StoreArtifact(data) if err != nil { t.Fatalf("StoreArtifact: %v", err) } expected := hash.NamingFor(mf.repo.DB()).New.Hash(data) if uuid != expected { t.Fatalf("UUID = %q, want %q", uuid, expected) } } func TestMockFossilPullFlow(t *testing.T) { mf := createMockFossil(t) // Seed an artifact in the master. data := []byte("artifact for pull test") uuid, _ := mf.StoreArtifact(data) ctx := context.Background() // Round 1: client sends pull — server should respond with igot. req1 := &xfer.Message{Cards: []xfer.Card{ &xfer.PullCard{ServerCode: "sc", ProjectCode: "pc"}, }} resp1, err := mf.Exchange(ctx, req1) if err != nil { t.Fatalf("Exchange round 1: %v", err) } var foundIGot bool for _, c := range resp1.Cards { if igot, ok := c.(*xfer.IGotCard); ok && igot.UUID == uuid { foundIGot = true } } if !foundIGot { t.Fatalf("expected igot card for %s in response", uuid) } // Round 2: client sends gimme — server should respond with file. req2 := &xfer.Message{Cards: []xfer.Card{ &xfer.PullCard{ServerCode: "sc", ProjectCode: "pc"}, &xfer.GimmeCard{UUID: uuid}, }} resp2, err := mf.Exchange(ctx, req2) if err != nil { t.Fatalf("Exchange round 2: %v", err) } var foundFile bool for _, c := range resp2.Cards { if fc, ok := c.(*xfer.FileCard); ok && fc.UUID == uuid { if string(fc.Content) != string(data) { t.Fatalf("file content mismatch") } foundFile = true } } if !foundFile { t.Fatalf("expected file card for %s in response", uuid) } } func TestMockFossilPushFlow(t *testing.T) { mf := createMockFossil(t) data := []byte("artifact from client push") uuid := hash.NamingFor(mf.repo.DB()).New.Hash(data) ctx := context.Background() // Round 1: client sends push + igot — server should gimme. // HandleSync requires both push AND pull for igot→gimme, // since gimme is a "I need this" response to igot. req1 := &xfer.Message{Cards: []xfer.Card{ &xfer.PushCard{ServerCode: "sc", ProjectCode: "pc"}, &xfer.PullCard{ServerCode: "sc", ProjectCode: "pc"}, &xfer.IGotCard{UUID: uuid}, }} resp1, err := mf.Exchange(ctx, req1) if err != nil { t.Fatalf("Exchange round 1: %v", err) } var foundGimme bool for _, c := range resp1.Cards { if gc, ok := c.(*xfer.GimmeCard); ok && gc.UUID == uuid { foundGimme = true } } if !foundGimme { t.Fatalf("expected gimme card for %s in response", uuid) } // Round 2: client sends push + file — server should store it. req2 := &xfer.Message{Cards: []xfer.Card{ &xfer.PushCard{ServerCode: "sc", ProjectCode: "pc"}, &xfer.FileCard{UUID: uuid, Content: data}, }} resp2, err := mf.Exchange(ctx, req2) if err != nil { t.Fatalf("Exchange round 2: %v", err) } for _, c := range resp2.Cards { if ec, ok := c.(*xfer.ErrorCard); ok { t.Fatalf("unexpected error card: %s", ec.Message) } } // Verify the artifact is in the master repo. _, ok := blob.Exists(mf.Repo().DB(), uuid) if !ok { t.Fatal("blob not found after push") } } func TestMockFossilPushBadUUID(t *testing.T) { mf := createMockFossil(t) ctx := context.Background() req := &xfer.Message{Cards: []xfer.Card{ &xfer.PushCard{ServerCode: "sc", ProjectCode: "pc"}, &xfer.FileCard{UUID: "0000000000000000000000000000000000000000", Content: []byte("data")}, }} resp, err := mf.Exchange(ctx, req) if err != nil { t.Fatalf("Exchange: %v", err) } var foundError bool for _, c := range resp.Cards { if _, ok := c.(*xfer.ErrorCard); ok { foundError = true } } if !foundError { t.Fatal("expected error card for UUID mismatch") } } func TestMockFossilRejectsBadLogin(t *testing.T) { mf := createMockFossil(t) ctx := context.Background() // Bad credentials should produce an auth error. req := &xfer.Message{Cards: []xfer.Card{ &xfer.LoginCard{User: "testuser", Nonce: "abc", Signature: "def"}, &xfer.PullCard{ServerCode: "sc", ProjectCode: "pc"}, }} resp, err := mf.Exchange(ctx, req) if err != nil { t.Fatalf("Exchange: %v", err) } foundAuthError := false for _, c := range resp.Cards { if ec, ok := c.(*xfer.ErrorCard); ok && ec.Message == "authentication failed" { foundAuthError = true } } if !foundAuthError { t.Fatal("expected authentication failed error for bad credentials") } } func TestMockFossilAnonymousPull(t *testing.T) { mf := createMockFossil(t) ctx := context.Background() // Anonymous (no login card) should work — nobody user has full caps. req := &xfer.Message{Cards: []xfer.Card{ &xfer.PullCard{ServerCode: "sc", ProjectCode: "pc"}, }} resp, err := mf.Exchange(ctx, req) if err != nil { t.Fatalf("Exchange: %v", err) } for _, c := range resp.Cards { if ec, ok := c.(*xfer.ErrorCard); ok { t.Fatalf("unexpected error: %s", ec.Message) } } }