package service
import (
"errors"
"strings"
"testing"
"time"
)
func TestLoadConfigAcceptsACompleteConfig(t *testing.T) {
cfg, err := LoadConfig(testIni(t, "/var/lib/spec"))
if err != nil {
t.Fatalf("LoadConfig: %v", err)
}
if cfg.Repos != "/var/lib/spec" || cfg.Cache != "/var/cache/spec" {
t.Errorf("paths = %q, %q", cfg.Repos, cfg.Cache)
}
if cfg.Origin != "https://spec.srht.bigb.es" {
t.Errorf("origin = %q", cfg.Origin)
}
if cfg.Instance.AgentEmail != "agent@spec.srht.bigb.es" {
t.Errorf("agent email = %q, want it derived from our own origin", cfg.Instance.AgentEmail)
}
if cfg.Instance.OwnerName != "bigbes" {
t.Errorf("owner = %q", cfg.Instance.OwnerName)
}
}
func TestLoadConfigTrimsTheOriginsTrailingSlash(t *testing.T) {
conf := testIni(t, "/var/lib/spec", "origin")
conf["spec.sr.ht"]["origin"] = "https://spec.srht.bigb.es/"
cfg, err := LoadConfig(conf)
if err != nil {
t.Fatalf("LoadConfig: %v", err)
}
if cfg.Origin != "https://spec.srht.bigb.es" {
t.Errorf("origin = %q, want the trailing slash gone", cfg.Origin)
}
}
// Every missing key must be named in one message: an operator fixes the config
// in one pass instead of discovering each gap on a separate restart.
func TestLoadConfigNamesEveryMissingKeyAtOnce(t *testing.T) {
conf := testIni(t, "/var/lib/spec", "repos", "cache", "owner-email")
_, err := LoadConfig(conf)
if !errors.Is(err, ErrIncompleteConfig) {
t.Fatalf("err = %v, want ErrIncompleteConfig", err)
}
for _, want := range []string{"[spec.sr.ht] repos", "[spec.sr.ht] cache", "[sr.ht] owner-email"} {
if !strings.Contains(err.Error(), want) {
t.Errorf("message does not name %q:\n%s", want, err)
}
}
if strings.Contains(err.Error(), "connection-string") {
t.Errorf("message names a key that was present:\n%s", err)
}
}
func TestLoadConfigRejectsUnusableValues(t *testing.T) {
tests := []struct {
name string
section string
key string
value string
want string
}{
{"relative repos", ConfigSection, "repos", "spec", "absolute path"},
{"relative cache", ConfigSection, "cache", "./cache", "absolute path"},
{"origin with no host", ConfigSection, "origin", "spec.srht.bigb.es", "no host"},
{"origin with a bad scheme", ConfigSection, "origin", "ftp://spec.srht.bigb.es", "http or https"},
{"blank owner", "sr.ht", "owner-name", " ", "[sr.ht] owner-name"},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
conf := testIni(t, "/var/lib/spec", tc.key)
conf[tc.section][tc.key] = tc.value
_, err := LoadConfig(conf)
if !errors.Is(err, ErrIncompleteConfig) {
t.Fatalf("err = %v, want ErrIncompleteConfig", err)
}
if !strings.Contains(err.Error(), tc.want) {
t.Errorf("message does not mention %q:\n%s", tc.want, err)
}
})
}
}
// A nil handle would make every agent token resolve as unknown, which looks
// exactly like a mass revocation.
func TestNewRefusesANilDatabaseHandle(t *testing.T) {
if _, err := New(testConfig(t, t.TempDir()), nil); err == nil {
t.Fatal("New with a nil handle succeeded")
}
}
func TestNewValidatesTheConfig(t *testing.T) {
cfg := testConfig(t, "relative/path")
if _, err := New(cfg, deadDB(t)); !errors.Is(err, ErrIncompleteConfig) {
t.Fatalf("err = %v, want ErrIncompleteConfig", err)
}
}
func TestServiceExposesItsWiring(t *testing.T) {
svc, root := newService(t)
if svc.ReposRoot() != root {
t.Errorf("ReposRoot = %q, want %q", svc.ReposRoot(), root)
}
if svc.Origin() != "https://spec.srht.bigb.es" {
t.Errorf("Origin = %q", svc.Origin())
}
if svc.Resolver() == nil || svc.Resolver().Owner() != "bigbes" {
t.Errorf("resolver = %v", svc.Resolver())
}
if svc.Store() == nil {
t.Error("store is nil")
}
if svc.grace != DefaultReconcileGrace || svc.now == nil {
t.Errorf("reconciler defaults not wired: grace=%v", svc.grace)
}
var _ time.Duration = DefaultReconcileInterval
}