Skip to content
quarry Artifact [227c5942a9]
Login

Artifact [227c5942a9]

Artifact 227c5942a9cff2f74a0de442410df7d018cd8e9c86a916c71ae716c3154c84fb:


package api

import (
	"context"
	"encoding/json"
	"errors"
	"strings"
	"testing"

	"craftdesign.group/quarry/internal/ops"
)

// memBridges is a ChatBridgeStore that remembers what it was told,
// credential included, so a test can prove the credential arrives at
// the store and never leaves it.
type memBridges struct {
	rows   map[string]ChatBridge
	secret map[string]string
}

func newMemBridges() *memBridges {
	return &memBridges{rows: map[string]ChatBridge{}, secret: map[string]string{}}
}

func (m *memBridges) SetChatBridge(repo, url, user, secret string) error {
	if !strings.HasPrefix(url, "http") {
		return errors.New("bridge url must be http or https")
	}
	m.rows[repo] = ChatBridge{Repo: repo, URL: url, User: user}
	m.secret[repo] = secret
	return nil
}

func (m *memBridges) DetachChatBridge(repo string) error {
	delete(m.rows, repo)
	delete(m.secret, repo)
	return nil
}

func (m *memBridges) ChatBridgeFor(repo string) (ChatBridge, bool, error) {
	b, ok := m.rows[repo]
	return b, ok, nil
}

func (m *memBridges) ChatBridges() ([]ChatBridge, error) {
	out := make([]ChatBridge, 0, len(m.rows))
	for _, b := range m.rows {
		out = append(out, b)
	}
	return out, nil
}

func bridgeRegistry(t *testing.T) (*ops.Registry, *memBridges) {
	t.Helper()
	repos := testRepos(wikiIndex(t))
	mustRepo(t, repos, "test").Code = "codeXYZ"
	store := newMemBridges()
	r := ops.NewRegistry()
	if err := RegisterAll(r, Deps{Version: "v", Repos: repos, Chat: &fakeChat{},
		ChatBridges: store}); err != nil {
		t.Fatal(err)
	}
	return r, store
}

// The password reaches the store and appears in nothing that comes
// back — not in the Out struct, and not in its JSON encoding, which is
// what a page would actually render.
func TestChatBridgeNeverReturnsTheCredential(t *testing.T) {
	r, store := bridgeRegistry(t)
	out, err := r.Invoke(context.Background(), "mirror.chatbridge", chatBridgeIn{
		Repo: "test", URL: "https://fossil.example/repo", User: "bot", Password: "s3cretpw"})
	if err != nil {
		t.Fatal(err)
	}
	if store.secret["test"] != "s3cretpw" {
		t.Fatalf("the credential did not reach the store: %q", store.secret["test"])
	}
	wire, err := json.Marshal(out)
	if err != nil {
		t.Fatal(err)
	}
	if strings.Contains(string(wire), "s3cretpw") {
		t.Fatalf("the credential came back on the wire: %s", wire)
	}
	got := out.(chatBridgeOut)
	if got.Bridge.URL != "https://fossil.example/repo" || got.Bridge.User != "bot" {
		t.Fatalf("bridge out = %+v", got.Bridge)
	}
}

// A bridge needs all three of url, user and password; a partial one is
// ErrInvalid, not a row that can never connect.
func TestChatBridgeRefusesAPartialConfiguration(t *testing.T) {
	r, _ := bridgeRegistry(t)
	for _, in := range []chatBridgeIn{
		{Repo: "test", URL: "https://x.example/r", User: "bot"},
		{Repo: "test", URL: "https://x.example/r", Password: "pw"},
		{Repo: "test", User: "bot", Password: "pw"},
		{Repo: "test", URL: "ssh://x.example/r", User: "bot", Password: "pw"},
	} {
		if _, err := r.Invoke(context.Background(), "mirror.chatbridge", in); !errors.Is(err, ops.ErrInvalid) {
			t.Fatalf("%+v: want ErrInvalid, got %v", in, err)
		}
	}
}

// Detaching forgets the bridge, and the op says so.
func TestChatBridgeDetach(t *testing.T) {
	r, store := bridgeRegistry(t)
	if _, err := r.Invoke(context.Background(), "mirror.chatbridge", chatBridgeIn{
		Repo: "test", URL: "https://x.example/r", User: "bot", Password: "pw"}); err != nil {
		t.Fatal(err)
	}
	out, err := r.Invoke(context.Background(), "mirror.chatbridge",
		chatBridgeIn{Repo: "test", Detach: true})
	if err != nil {
		t.Fatal(err)
	}
	if !out.(chatBridgeOut).Detached {
		t.Fatalf("detach out = %+v", out)
	}
	if _, ok := store.rows["test"]; ok {
		t.Fatal("detach left the bridge configured")
	}
	if _, ok := store.secret["test"]; ok {
		t.Fatal("detach left the credential behind")
	}
}

// The op is admin-only and named where the RFC says.
func TestChatBridgeSurface(t *testing.T) {
	r, _ := bridgeRegistry(t)
	op, ok := r.Lookup("mirror.chatbridge")
	if !ok {
		t.Fatal("mirror.chatbridge is not registered")
	}
	if op.Caps != "a" || op.Auth != ops.Admin || op.Method != "POST" ||
		op.Pattern != "/api/v1/r/{repo}/mirror/chatbridge" {
		t.Fatalf("surface = caps %q auth %v %s %s", op.Caps, op.Auth, op.Method, op.Pattern)
	}
}

// A bridge's state is the one word a status line needs, and it tells
// "quiet" from "broken".
func TestChatBridgeState(t *testing.T) {
	for _, c := range []struct {
		b    ChatBridge
		want string
	}{
		{ChatBridge{}, "connecting"},
		{ChatBridge{At: "2026-08-29T00:00:00Z"}, "connected"},
		{ChatBridge{At: "2026-08-29T00:00:00Z", LastErr: "boom"}, "failing"},
	} {
		if got := c.b.State(); got != c.want {
			t.Fatalf("%+v state = %q, want %q", c.b, got, c.want)
		}
	}
}

// The forge's own rule about what it will fetch, checked at the OP so
// it holds whatever store is wired. The fake store below accepts
// anything that starts with "http", so a check that lived only there
// would pass this test while production refused — or worse, the
// reverse.
func TestChatBridgeURLRuleIsTheOpsOwn(t *testing.T) {
	r, store := bridgeRegistry(t)
	for _, bad := range []string{
		"httpx://fossil.example/r",            // not a scheme quarry fetches
		"http://bot:[email protected]/r", // credentials belong in the fields
		"http://[email protected]/r",
		"https:///nohost",
		"not a url at all",
	} {
		_, err := r.Invoke(context.Background(), "mirror.chatbridge", chatBridgeIn{
			Repo: "test", URL: bad, User: "bot", Password: "pw"})
		if !errors.Is(err, ops.ErrInvalid) {
			t.Fatalf("%q: want ErrInvalid from the op, got %v", bad, err)
		}
		if _, stored := store.rows["test"]; stored {
			t.Fatalf("%q reached the store", bad)
		}
	}
	if _, err := r.Invoke(context.Background(), "mirror.chatbridge", chatBridgeIn{
		Repo: "test", URL: "https://fossil.example/r", User: "bot", Password: "pw"}); err != nil {
		t.Fatalf("a good url was refused: %v", err)
	}
}