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)
}
})
}