150 lines
4.0 KiB
Go
150 lines
4.0 KiB
Go
package site
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"sync"
|
|
"testing"
|
|
|
|
"github.com/iceBear67/simplepages/internal/store"
|
|
)
|
|
|
|
func TestRegistryLookupAndDelete(t *testing.T) {
|
|
r := NewRegistry()
|
|
if _, ok := r.Lookup("demo"); ok {
|
|
t.Fatal("an empty registry answered a lookup")
|
|
}
|
|
if r.Len() != 0 {
|
|
t.Fatalf("Len = %d on an empty registry", r.Len())
|
|
}
|
|
|
|
sp := r.Put(&store.Project{ID: 7, Name: "demo", IndexFile: "index.html"})
|
|
got, ok := r.Lookup("demo")
|
|
if !ok || got != sp {
|
|
t.Fatal("Put did not publish the project it returned")
|
|
}
|
|
if got.ID != 7 || got.Name != "demo" {
|
|
t.Errorf("entry = %+v", got)
|
|
}
|
|
if r.Len() != 1 {
|
|
t.Errorf("Len = %d, want 1", r.Len())
|
|
}
|
|
|
|
r.Delete("demo")
|
|
if _, ok := r.Lookup("demo"); ok {
|
|
t.Error("a deleted project still resolves")
|
|
}
|
|
r.Delete("demo") // deleting twice is not an error
|
|
r.Delete("never-existed")
|
|
}
|
|
|
|
// A settings change must not disturb what the project is currently serving: the
|
|
// entry is updated in place rather than rebuilt, so the active pointer survives.
|
|
func TestRegistryPutKeepsTheActiveDeployment(t *testing.T) {
|
|
r := NewRegistry()
|
|
p := &store.Project{ID: 1, Name: "demo", IndexFile: "index.html"}
|
|
sp := r.Put(p)
|
|
d := fixture("index.html")
|
|
sp.Activate(d)
|
|
|
|
p2 := &store.Project{ID: 1, Name: "demo", IndexFile: "main.html", SPAFallback: true}
|
|
again := r.Put(p2)
|
|
if again != sp {
|
|
t.Fatal("Put replaced the live entry instead of updating it")
|
|
}
|
|
if again.Active() != d {
|
|
t.Fatal("updating the settings dropped the active deployment")
|
|
}
|
|
if cfg := again.Config(); cfg.IndexFile != "main.html" || !cfg.SPAFallback {
|
|
t.Errorf("config = %+v, want the new settings", cfg)
|
|
}
|
|
}
|
|
|
|
// A project recreated under the same name is a different project, so its entry
|
|
// must start empty rather than inherit the old one's deployment.
|
|
func TestRegistryPutReplacesAnEntryWithADifferentID(t *testing.T) {
|
|
r := NewRegistry()
|
|
sp := r.Put(&store.Project{ID: 1, Name: "demo"})
|
|
sp.Activate(fixture("index.html"))
|
|
|
|
again := r.Put(&store.Project{ID: 2, Name: "demo"})
|
|
if again == sp {
|
|
t.Fatal("a different project id reused the old entry")
|
|
}
|
|
if again.Active() != nil {
|
|
t.Error("the new project inherited the old project's deployment")
|
|
}
|
|
}
|
|
|
|
func TestRegistryReplace(t *testing.T) {
|
|
r := NewRegistry()
|
|
r.Put(&store.Project{ID: 1, Name: "gone"})
|
|
r.Replace([]*Project{
|
|
NewProject(&store.Project{ID: 2, Name: "a"}),
|
|
NewProject(&store.Project{ID: 3, Name: "b"}),
|
|
})
|
|
if _, ok := r.Lookup("gone"); ok {
|
|
t.Error("Replace kept a project that is not in the new set")
|
|
}
|
|
if r.Len() != 2 {
|
|
t.Fatalf("Len = %d, want 2", r.Len())
|
|
}
|
|
names := map[string]bool{}
|
|
for _, p := range r.Projects() {
|
|
names[p.Name] = true
|
|
}
|
|
if !names["a"] || !names["b"] {
|
|
t.Errorf("Projects = %v", names)
|
|
}
|
|
}
|
|
|
|
// The API's ownership guard compares resolved ids, so this is the lookup that
|
|
// decides whether one project's key can touch another's deployment.
|
|
func TestRegistryResolveProject(t *testing.T) {
|
|
r := NewRegistry()
|
|
r.Put(&store.Project{ID: 42, Name: "demo"})
|
|
|
|
id, err := r.ResolveProject(context.Background(), "demo")
|
|
if err != nil || id != 42 {
|
|
t.Fatalf("ResolveProject = (%d, %v), want (42, nil)", id, err)
|
|
}
|
|
if _, err := r.ResolveProject(context.Background(), "other"); !errors.Is(err, store.ErrNotFound) {
|
|
t.Fatalf("ResolveProject on an unknown name = %v, want ErrNotFound", err)
|
|
}
|
|
}
|
|
|
|
// Readers take no lock at all, so this is worth running under -race: a writer
|
|
// mutating the map in place instead of cloning it would show up here.
|
|
func TestRegistryConcurrentReadersAndWriters(t *testing.T) {
|
|
r := NewRegistry()
|
|
r.Put(&store.Project{ID: 1, Name: "stable"})
|
|
stop := make(chan struct{})
|
|
var wg sync.WaitGroup
|
|
|
|
for range 8 {
|
|
wg.Add(1)
|
|
go func() {
|
|
defer wg.Done()
|
|
for {
|
|
select {
|
|
case <-stop:
|
|
return
|
|
default:
|
|
}
|
|
if p, ok := r.Lookup("stable"); !ok || p.ID != 1 {
|
|
t.Error("the stable project vanished while other projects changed")
|
|
return
|
|
}
|
|
r.Lookup("churn")
|
|
r.Len()
|
|
}
|
|
}()
|
|
}
|
|
for i := range 500 {
|
|
r.Put(&store.Project{ID: int64(i + 2), Name: "churn"})
|
|
r.Delete("churn")
|
|
}
|
|
close(stop)
|
|
wg.Wait()
|
|
}
|