package web import ( "context" "net/http" "net/http/httptest" "strings" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "sourcecraft.dev/bigbes/sr-ht-dolt/core" "sourcecraft.dev/bigbes/sr-ht-dolt/db" ) // postInternalCreate calls handleInternalCreate directly with a JSON body, // bypassing the router and thus the internalauth guard. The guard's own // behaviour is sr-ht-ecore's to test, and its agreement with the side that mints // the header is cmd/dolt-git-hook's; what is left for here is the endpoint's // business logic, which needs neither the network key nor a loaded // internal-network list. func (h *harness) postInternalCreate(body string) *httptest.ResponseRecorder { req := httptest.NewRequest("POST", "/internal/repos", strings.NewReader(body)) req.Header.Set("Content-Type", "application/json") rec := httptest.NewRecorder() h.app.handleInternalCreate(rec, req) return rec } // TestInternalCreateRouteIsGuarded is the one assertion the suite above cannot // make by calling the handler: that /internal/repos is behind the guard at all. // Nothing here fills the internal network list, so httptest's default TEST-NET-1 // source address is external and the call is refused before its (absent) token // matters — which is exactly the assertion. An unmounted guard would answer this // body with a 422 for the unknown owner instead. func TestInternalCreateRouteIsGuarded(t *testing.T) { h := newHarness(t) req := httptest.NewRequest("POST", "/internal/repos", strings.NewReader(`{"owner":"alice","name":"widgets"}`)) req.Header.Set("Content-Type", "application/json") rec := httptest.NewRecorder() h.router.ServeHTTP(rec, req) assert.Equal(t, http.StatusUnauthorized, rec.Code, rec.Body.String()) assert.Empty(t, h.store.createdCalls, "an unauthenticated call must not reach the handler") } func TestInternalCreateProvisions(t *testing.T) { h := newHarness(t) h.users.byName["alice"] = &core.Caller{UserID: 7, Username: "alice"} rec := h.postInternalCreate(`{"owner":"~alice","name":"widgets","description":"hi"}`) if rec.Code != http.StatusCreated { t.Fatalf("create: got %d, want 201 (body: %s)", rec.Code, rec.Body.String()) } if !strings.Contains(rec.Body.String(), `"created":true`) { t.Fatalf("expected created:true, got %s", rec.Body.String()) } if !strings.Contains(rec.Body.String(), "/~alice/widgets") { t.Fatalf("expected companion URL in body, got %s", rec.Body.String()) } // Row inserted with the resolved owner id and default (PRIVATE) visibility. if len(h.store.createdCalls) != 1 { t.Fatalf("expected 1 CreateRepo call, got %d", len(h.store.createdCalls)) } got := h.store.createdCalls[0] if got.OwnerID != 7 || got.OwnerName != "alice" || got.Name != "widgets" { t.Fatalf("unexpected repo: %+v", got) } if got.Visibility != core.VisibilityPrivate { t.Fatalf("default visibility: got %q, want PRIVATE", got.Visibility) } // On-disk store initialized at the mapped path. if len(h.stores.initCalls) != 1 || !strings.HasSuffix(h.stores.initCalls[0], "/~alice/widgets") { t.Fatalf("expected InitStore at ~alice/widgets, got %v", h.stores.initCalls) } } func TestInternalCreateIdempotent(t *testing.T) { h := newHarness(t) h.users.byName["alice"] = &core.Caller{UserID: 7, Username: "alice"} h.store.add(&core.Repo{Name: "widgets", OwnerID: 7, OwnerName: "alice", Path: "/p", Visibility: core.VisibilityPrivate}) rec := h.postInternalCreate(`{"owner":"alice","name":"widgets"}`) if rec.Code != http.StatusOK { t.Fatalf("existing companion: got %d, want 200 (body: %s)", rec.Code, rec.Body.String()) } if !strings.Contains(rec.Body.String(), `"created":false`) { t.Fatalf("expected created:false, got %s", rec.Body.String()) } // Must NOT touch disk when the row already exists. if len(h.stores.initCalls) != 0 { t.Fatalf("InitStore must not run for an existing companion, got %v", h.stores.initCalls) } } func TestInternalCreateRollsBackOnStoreFailure(t *testing.T) { h := newHarness(t) h.users.byName["alice"] = &core.Caller{UserID: 7, Username: "alice"} h.stores.initErr = errInitBoom rec := h.postInternalCreate(`{"owner":"alice","name":"widgets"}`) if rec.Code != http.StatusInternalServerError { t.Fatalf("store failure: got %d, want 500", rec.Code) } // The metadata row inserted before InitStore must be rolled back. if len(h.store.deletedRepos) != 1 { t.Fatalf("expected rollback DeleteRepo, got deletes=%v", h.store.deletedRepos) } if _, err := h.store.GetRepoByOwnerAndName(nil, "alice", "widgets"); err != db.ErrNotFound { t.Fatalf("expected repo removed after rollback, err=%v", err) } } func TestInternalCreateUnknownOwner(t *testing.T) { h := newHarness(t) // alice is not registered in fakeUsers -> LookupUser fails. rec := h.postInternalCreate(`{"owner":"alice","name":"widgets"}`) if rec.Code != http.StatusUnprocessableEntity { t.Fatalf("unknown owner: got %d, want 422 (body: %s)", rec.Code, rec.Body.String()) } if len(h.store.createdCalls) != 0 { t.Fatalf("must not create a row for an unresolvable owner") } } func TestInternalCreateRejectsBadName(t *testing.T) { h := newHarness(t) h.users.byName["alice"] = &core.Caller{UserID: 7, Username: "alice"} rec := h.postInternalCreate(`{"owner":"alice","name":".."}`) if rec.Code != http.StatusBadRequest { t.Fatalf("bad name: got %d, want 400", rec.Code) } } // fakeGit is a canned GitDescriber: one description per owner/name key, with // ok=false for anything unlisted (no git twin / git.sr.ht down). type fakeGit struct { byRepo map[string]string } func (g *fakeGit) Description(_ context.Context, owner, name string) (string, bool) { d, ok := g.byRepo[owner+"/"+name] return d, ok } func TestInternalCreateMirrorsGitDescription(t *testing.T) { h := newHarness(t) h.users.byName["alice"] = &core.Caller{UserID: 7, Username: "alice"} h.app.cfg.Git = &fakeGit{byRepo: map[string]string{"alice/widgets": "widget factory"}} rec := h.postInternalCreate(`{"owner":"alice","name":"widgets"}`) require.Equal(t, http.StatusCreated, rec.Code, rec.Body.String()) require.Len(t, h.store.createdCalls, 1) assert.Equal(t, "widget factory", h.store.createdCalls[0].Description) } func TestInternalCreateSyncsChangedGitDescription(t *testing.T) { h := newHarness(t) h.users.byName["alice"] = &core.Caller{UserID: 7, Username: "alice"} h.store.add(&core.Repo{Name: "widgets", Description: "stale", OwnerID: 7, OwnerName: "alice", Path: "/p", Visibility: core.VisibilityUnlisted}) h.app.cfg.Git = &fakeGit{byRepo: map[string]string{"alice/widgets": "fresh"}} rec := h.postInternalCreate(`{"owner":"alice","name":"widgets"}`) require.Equal(t, http.StatusOK, rec.Code, rec.Body.String()) got, err := h.store.GetRepoByOwnerAndName(nil, "alice", "widgets") require.NoError(t, err) assert.Equal(t, "fresh", got.Description) assert.Equal(t, core.VisibilityUnlisted, got.Visibility, "sync must not touch visibility") } func TestInternalCreateEmptyGitDescriptionKeepsLocal(t *testing.T) { h := newHarness(t) h.users.byName["alice"] = &core.Caller{UserID: 7, Username: "alice"} h.store.add(&core.Repo{Name: "widgets", Description: "set in dolt", OwnerID: 7, OwnerName: "alice", Path: "/p", Visibility: core.VisibilityPrivate}) // Twin exists but carries no description: ("", true) must not clobber. h.app.cfg.Git = &fakeGit{byRepo: map[string]string{"alice/widgets": ""}} rec := h.postInternalCreate(`{"owner":"alice","name":"widgets"}`) require.Equal(t, http.StatusOK, rec.Code, rec.Body.String()) got, err := h.store.GetRepoByOwnerAndName(nil, "alice", "widgets") require.NoError(t, err) assert.Equal(t, "set in dolt", got.Description) } var errInitBoom = &boomError{} type boomError struct{} func (*boomError) Error() string { return "boom" }