package service
import (
"context"
"errors"
"os"
"testing"
"sourcecraft.dev/bigbes/sr-ht-spec/core"
"sourcecraft.dev/bigbes/sr-ht-spec/gitx"
)
// A crash between the repository and the row leaves content that is merely
// unlisted; a *failure* of the row insert leaves nothing at all, because the
// repository is seconds old, empty and named nowhere yet.
func TestCreateSpaceRemovesTheRepositoryWhenTheRowFails(t *testing.T) {
svc, root := newService(t) // its database handle cannot be reached
dir := gitx.DiskPath(root, fxSpace)
_, err := svc.CreateSpace(context.Background(), fxSpace)
if err == nil {
t.Fatal("CreateSpace succeeded with an unreachable database")
}
if _, statErr := os.Stat(dir); !os.IsNotExist(statErr) {
t.Fatalf("repository at %s survived a failed row insert (stat: %v)", dir, statErr)
}
}
func TestCreateSpaceRejectsUnsafeNames(t *testing.T) {
svc, _ := newService(t)
ctx := context.Background()
for _, ref := range []core.SpaceRef{
{Owner: "..", Name: "rfcs"},
{Owner: "bigbes", Name: "../../etc"},
{Owner: "bigbes", Name: ""},
{Owner: "", Name: "rfcs"},
} {
if _, err := svc.CreateSpace(ctx, ref); err == nil {
t.Errorf("CreateSpace(%+v) succeeded", ref)
}
}
}
func TestSpaceLifecycle(t *testing.T) {
svc, root := newTestService(t)
ctx := context.Background()
sp, err := svc.CreateSpace(ctx, fxSpace)
if err != nil {
t.Fatalf("CreateSpace: %v", err)
}
if sp.ID == 0 {
t.Error("space has no row id")
}
if sp.Repo.Dir() != gitx.DiskPath(root, fxSpace) {
t.Errorf("dir = %q", sp.Repo.Dir())
}
// The approved head resolves from the moment the space exists, so no
// reader, reconciler or merge has to special-case an unborn branch.
if _, err := sp.Repo.ApprovedHead(ctx); err != nil {
t.Errorf("approved head: %v", err)
}
if _, err := svc.CreateSpace(ctx, fxSpace); !errors.Is(err, ErrSpaceExists) {
t.Errorf("second CreateSpace err = %v, want ErrSpaceExists", err)
}
opened, err := svc.OpenSpace(ctx, fxSpace)
if err != nil {
t.Fatalf("OpenSpace: %v", err)
}
if opened.ID != sp.ID || opened.Repo == nil {
t.Errorf("opened = %+v", opened)
}
if _, err := svc.OpenSpace(ctx, core.SpaceRef{Owner: "bigbes", Name: "nope"}); !errors.Is(err, ErrNotFound) {
t.Errorf("OpenSpace of a missing space err = %v, want ErrNotFound", err)
}
spaces, err := svc.ListSpaces(ctx)
if err != nil {
t.Fatalf("ListSpaces: %v", err)
}
if len(spaces) != 1 || spaces[0].Ref != fxSpace {
t.Fatalf("spaces = %+v", spaces)
}
if spaces[0].Repo != nil {
t.Error("ListSpaces opened repositories; listing must stay cheap")
}
}
// A row whose repository is missing is a half-created space, not a space. It is
// reported rather than served: every document read would fail individually.
func TestOpenSpaceRefusesARowWithNoRepository(t *testing.T) {
svc, root := newTestService(t)
ctx := context.Background()
if _, err := svc.CreateSpace(ctx, fxSpace); err != nil {
t.Fatalf("CreateSpace: %v", err)
}
if err := os.RemoveAll(gitx.DiskPath(root, fxSpace)); err != nil {
t.Fatalf("remove repository: %v", err)
}
if _, err := svc.OpenSpace(ctx, fxSpace); !errors.Is(err, ErrNotFound) {
t.Fatalf("err = %v, want ErrNotFound", err)
}
}