package memory_test import ( "errors" "testing" "codeberg.org/lindenii/furgit/internal/testgit" objectid "codeberg.org/lindenii/furgit/object/id" "codeberg.org/lindenii/furgit/ref" refstore "codeberg.org/lindenii/furgit/ref/store" "codeberg.org/lindenii/furgit/ref/store/memory" ) // Unlike the public ResolveToDetached, // this one does not resolve symbolic refs. func resolveDetached(t *testing.T, store *memory.Store, name string) ref.Detached { t.Helper() resolved, err := store.Resolve(name) if err != nil { t.Fatalf("Resolve(%q): %v", name, err) } detached, ok := resolved.(ref.Detached) if !ok { t.Fatalf("Resolve(%q) = %T, want ref.Detached", name, resolved) } return detached } func TestReadListAndResolveSymbolic(t *testing.T) { t.Parallel() //nolint:thelper testgit.ForEachAlgorithm(t, func(t *testing.T, algo objectid.Algorithm) { t.Parallel() store, err := memory.New(algo) if err != nil { t.Fatalf("memory.New: %v", err) } mainID := algo.Sum([]byte("main")) tx, err := store.BeginTransaction() if err != nil { t.Fatalf("BeginTransaction: %v", err) } err = tx.Create("refs/heads/main", mainID) if err != nil { t.Fatalf("Create(main): %v", err) } err = tx.CreateSymbolic("HEAD", "refs/heads/main") if err != nil { t.Fatalf("CreateSymbolic(HEAD): %v", err) } err = tx.Commit() if err != nil { t.Fatalf("Commit seed refs: %v", err) } head, err := store.Resolve("HEAD") if err != nil { t.Fatalf("Resolve(HEAD): %v", err) } symbolic, ok := head.(ref.Symbolic) if !ok { t.Fatalf("Resolve(HEAD) = %T, want ref.Symbolic", head) } if symbolic.Target != "refs/heads/main" { t.Fatalf("HEAD target = %q, want refs/heads/main", symbolic.Target) } detached, err := store.ResolveToDetached("HEAD") if err != nil { t.Fatalf("ResolveToDetached(HEAD): %v", err) } if detached.ID != mainID { t.Fatalf("ResolveToDetached(HEAD) ID = %v, want %v", detached.ID, mainID) } listed, err := store.List("") if err != nil { t.Fatalf("List: %v", err) } if len(listed) != 2 || listed[0].Name() != "HEAD" || listed[1].Name() != "refs/heads/main" { t.Fatalf("List names = %v, want [HEAD refs/heads/main]", listed) } }) } func TestTransactionRejectLeavesStoreUnchanged(t *testing.T) { t.Parallel() //nolint:thelper testgit.ForEachAlgorithm(t, func(t *testing.T, algo objectid.Algorithm) { t.Parallel() store, err := memory.New(algo) if err != nil { t.Fatalf("memory.New: %v", err) } mainID := algo.Sum([]byte("main")) devID := algo.Sum([]byte("dev")) nextID := algo.Sum([]byte("next")) wrongOld := algo.Sum([]byte("wrong")) tx, err := store.BeginTransaction() if err != nil { t.Fatalf("BeginTransaction: %v", err) } err = tx.Create("refs/heads/main", mainID) if err != nil { t.Fatalf("Create(main): %v", err) } err = tx.Create("refs/heads/dev", devID) if err != nil { t.Fatalf("Create(dev): %v", err) } err = tx.Commit() if err != nil { t.Fatalf("Commit seed refs: %v", err) } tx, err = store.BeginTransaction() if err != nil { t.Fatalf("BeginTransaction: %v", err) } err = tx.Update("refs/heads/main", nextID, mainID) if err != nil { t.Fatalf("Update(main): %v", err) } err = tx.Update("refs/heads/dev", nextID, wrongOld) if err != nil { t.Fatalf("Update(dev): %v", err) } err = tx.Commit() if err == nil { t.Fatalf("Commit succeeded, want incorrect old value error") } var oldValueErr *refstore.IncorrectOldValueError if !errors.As(err, &oldValueErr) { t.Fatalf("Commit error = %T %v, want IncorrectOldValueError", err, err) } if got := resolveDetached(t, store, "refs/heads/main").ID; got != mainID { t.Fatalf("main after rejected transaction = %v, want %v", got, mainID) } if got := resolveDetached(t, store, "refs/heads/dev").ID; got != devID { t.Fatalf("dev after rejected transaction = %v, want %v", got, devID) } }) } func TestBatchRejectsDuplicateResolvedTargetAndAppliesRemainder(t *testing.T) { t.Parallel() //nolint:thelper testgit.ForEachAlgorithm(t, func(t *testing.T, algo objectid.Algorithm) { t.Parallel() store, err := memory.New(algo) if err != nil { t.Fatalf("memory.New: %v", err) } mainID := algo.Sum([]byte("main")) devID := algo.Sum([]byte("dev")) nextMainID := algo.Sum([]byte("next-main")) nextDevID := algo.Sum([]byte("next-dev")) aliasID := algo.Sum([]byte("alias")) tx, err := store.BeginTransaction() if err != nil { t.Fatalf("BeginTransaction: %v", err) } err = tx.Create("refs/heads/main", mainID) if err != nil { t.Fatalf("Create(main): %v", err) } err = tx.Create("refs/heads/dev", devID) if err != nil { t.Fatalf("Create(dev): %v", err) } err = tx.CreateSymbolic("refs/heads/alias", "refs/heads/main") if err != nil { t.Fatalf("CreateSymbolic(alias): %v", err) } err = tx.Commit() if err != nil { t.Fatalf("Commit seed refs: %v", err) } batch, err := store.BeginBatch() if err != nil { t.Fatalf("BeginBatch: %v", err) } err = batch.Update("refs/heads/main", nextMainID, mainID) if err != nil { t.Fatalf("Update(main): %v", err) } err = batch.Update("refs/heads/alias", aliasID, mainID) if err != nil { t.Fatalf("Update(alias): %v", err) } err = batch.Update("refs/heads/dev", nextDevID, devID) if err != nil { t.Fatalf("Update(dev): %v", err) } results, err := batch.Apply() if err != nil { t.Fatalf("Apply: %v", err) } if len(results) != 3 { t.Fatalf("len(results) = %d, want 3", len(results)) } if results[0].Status != refstore.BatchStatusApplied { t.Fatalf("results[0].Status = %v, want applied", results[0].Status) } if results[1].Status != refstore.BatchStatusRejected { t.Fatalf("results[1].Status = %v, want rejected", results[1].Status) } var duplicateErr *refstore.DuplicateUpdateError if !errors.As(results[1].Error, &duplicateErr) { t.Fatalf("results[1].Error = %T %v, want DuplicateUpdateError", results[1].Error, results[1].Error) } if results[2].Status != refstore.BatchStatusApplied { t.Fatalf("results[2].Status = %v, want applied", results[2].Status) } if got := resolveDetached(t, store, "refs/heads/main").ID; got != nextMainID { t.Fatalf("main after batch = %v, want %v", got, nextMainID) } if got := resolveDetached(t, store, "refs/heads/dev").ID; got != nextDevID { t.Fatalf("dev after batch = %v, want %v", got, nextDevID) } resolved, err := store.Resolve("refs/heads/alias") if err != nil { t.Fatalf("Resolve(alias): %v", err) } symbolic, ok := resolved.(ref.Symbolic) if !ok { t.Fatalf("Resolve(alias) = %T, want ref.Symbolic", resolved) } if symbolic.Target != "refs/heads/main" { t.Fatalf("alias target = %q, want refs/heads/main", symbolic.Target) } }) }