package main import ( "bytes" "crypto/ed25519" "encoding/base64" "encoding/json" "io" "net/http" "net/http/httptest" "strings" "testing" "time" "github.com/fernet/fernet-go" "github.com/vaughan0/go-ini" "sourcecraft.dev/bigbes/sr-ht-core/crypto" ) // initTestCrypto installs a random network key + webhooks seed into the shared // crypto globals, mirroring the sr-ht-core test pattern, so createCompanion's // crypto.Encrypt and the test server's DecryptWithExpiration share a keyset. func initTestCrypto(t *testing.T) { t.Helper() var fk fernet.Key if err := fk.Generate(); err != nil { t.Fatalf("fernet generate: %v", err) } seed := make([]byte, ed25519.SeedSize) for i := range seed { seed[i] = byte(i + 1) } crypto.InitCrypto(ini.File{ "sr.ht": ini.Section{"network-key": fk.Encode()}, "webhooks": ini.Section{"private-key": base64.StdEncoding.EncodeToString(seed)}, }) } func samplePush() pushContext { var pc pushContext pc.Repo.Name = "widgets" pc.Repo.OwnerName = "alice" pc.Repo.Visibility = "private" return pc } // decodeInternalAuth verifies an incoming "Internal " header the same way // dolt.sr.ht's internalAuthGuard does and returns the decoded claims. func decodeInternalAuth(t *testing.T, r *http.Request) internalAuth { t.Helper() parts := strings.SplitN(r.Header.Get("Authorization"), " ", 2) if len(parts) != 2 || !strings.EqualFold(parts[0], "internal") { t.Fatalf("missing/invalid Internal auth header: %q", r.Header.Get("Authorization")) } payload := crypto.DecryptWithExpiration([]byte(parts[1]), 30*time.Second) if payload == nil { t.Fatalf("auth token did not decrypt (wrong key or expired)") } var ia internalAuth if err := json.Unmarshal(payload, &ia); err != nil { t.Fatalf("auth payload not JSON: %v", err) } return ia } func TestCreateCompanionSignsAndAnnouncesOnCreate(t *testing.T) { initTestCrypto(t) var gotBody map[string]string srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { if r.URL.Path != "/internal/repos" || r.Method != http.MethodPost { t.Errorf("unexpected request %s %s", r.Method, r.URL.Path) } ia := decodeInternalAuth(t, r) if ia.ClientID != "git.sr.ht" || ia.NodeID != "dolt-git-hook" { t.Errorf("unexpected auth claims: %+v", ia) } raw, _ := io.ReadAll(r.Body) _ = json.Unmarshal(raw, &gotBody) w.WriteHeader(http.StatusCreated) _, _ = w.Write([]byte(`{"url":"https://dolt.example/~alice/widgets","created":true}`)) })) defer srv.Close() var out bytes.Buffer createCompanion(&out, srv.URL, samplePush()) if gotBody["owner"] != "alice" || gotBody["name"] != "widgets" { t.Fatalf("unexpected request body: %+v", gotBody) } if gotBody["visibility"] != "PRIVATE" { t.Fatalf("visibility not normalized to PRIVATE: %q", gotBody["visibility"]) } if !strings.Contains(out.String(), "NOTICE") || !strings.Contains(out.String(), "dolt clone https://dolt.example/~alice/widgets") { t.Fatalf("expected clone notice, got: %q", out.String()) } } func TestCreateCompanionQuietWhenExists(t *testing.T) { initTestCrypto(t) srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { decodeInternalAuth(t, r) // still must be authenticated w.WriteHeader(http.StatusOK) _, _ = w.Write([]byte(`{"url":"https://dolt.example/~alice/widgets","created":false}`)) })) defer srv.Close() var out bytes.Buffer createCompanion(&out, srv.URL, samplePush()) if out.Len() != 0 { t.Fatalf("expected no output for existing companion, got: %q", out.String()) } } func TestCreateCompanionWarnsOnError(t *testing.T) { initTestCrypto(t) srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusInternalServerError) _, _ = w.Write([]byte("boom")) })) defer srv.Close() var out bytes.Buffer createCompanion(&out, srv.URL, samplePush()) if !strings.Contains(out.String(), "provisioning failed (500)") { t.Fatalf("expected failure warning, got: %q", out.String()) } } func TestNormalizeVisibility(t *testing.T) { cases := map[string]string{ "private": "PRIVATE", "PUBLIC": "PUBLIC", "Unlisted": "UNLISTED", "": "", "bogus": "", } for in, want := range cases { if got := normalizeVisibility(in); got != want { t.Errorf("normalizeVisibility(%q) = %q, want %q", in, got, want) } } }