Artifact 69f3c2ab6cfe79179f2d97ce25c1ee8ecbb149467be1b8ae93e0e1ffe4ca136c:
- File
dst/mock_fossil_test.go
— part of check-in
[c42a2b90c6]
at
2026-10-13 05:59:46
on branch main
— fix(manifest,hash): verify the R card at commit; reject an unrecognized hash-policy (#396)
* fix(manifest,hash): verify the R card at commit; reject an unrecognized hash-policy
A check-in reads its manifest back and requires the R card to equal the digest recomputed over the resolved tree, byte for byte (artifact-format-01 6.3.4, AF-6.3-5). A hash-policy setting outside the ordinals 0 through 4 is now an error instead of a fall back to the default (7.1, AF-7.1-2).
Tracked in #387.
* fix(manifest,repo): drive the commit R check from conformance; refuse a bad hash-policy on write (user: [email protected], size: 5645) [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{}, testPC) if err != nil { t.Fatalf("repo.Create: %v", err) } t.Cleanup(func() { r.Close() }) grantLeavesPush(t, r) 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.MustNamingFor(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: testPC}, }} 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.PragmaCard{Name: "client-version", Values: []string{"22800", "20260315", "120000"}}, &xfer.PullCard{ServerCode: "sc", ProjectCode: testPC}, &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.MustNamingFor(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: testPC}, &xfer.PullCard{ServerCode: "sc", ProjectCode: testPC}, &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: testPC}, &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: testPC}, &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: testPC}, }} 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 == "login failed" { foundAuthError = true } } if !foundAuthError { t.Fatal("expected login 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: testPC}, }} 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) } } }