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 internalAuthGuard, which is exercised
// separately). The auth guard needs the process-global crypto/config state a
// unit test does not set up; the business logic under test here does not.
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
}
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" }