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