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 }