package towel import ( "context" "os" "strings" "sync" "testing" ) func TestContextAccessorsRoundTrip(t *testing.T) { ctx := context.Background() ctx = WithActor(ctx, "user-1") ctx = WithOrganization(ctx, "org-9") ctx = WithCollection(ctx, "col-3") ctx = WithLocale(ctx, "pl") if got, ok := Actor(ctx); !ok || got != "user-1" { t.Fatalf("Actor = (%q, %v)", got, ok) } if got, ok := Organization(ctx); !ok || got != "org-9" { t.Fatalf("Organization = (%q, %v)", got, ok) } if got, ok := Collection(ctx); !ok || got != "col-3" { t.Fatalf("Collection = (%q, %v)", got, ok) } if got, ok := Locale(ctx); !ok || got != "pl" { t.Fatalf("Locale = (%q, %v)", got, ok) } other := context.Background() if _, ok := Actor(other); ok { t.Fatal("Actor leaked across contexts") } } func TestNestedContextIsolation(t *testing.T) { parent := WithActor(context.Background(), "parent") parent = WithOrganization(parent, "org-parent") child := WithActor(parent, "child") child = WithCollection(child, "col-child") child = WithLocale(child, "pl") if got, ok := Actor(parent); !ok || got != "parent" { t.Fatalf("parent Actor mutated: (%q, %v)", got, ok) } if got, ok := Actor(child); !ok || got != "child" { t.Fatalf("child Actor = (%q, %v)", got, ok) } if got, ok := Organization(child); !ok || got != "org-parent" { t.Fatalf("child should inherit organization, got (%q, %v)", got, ok) } if _, ok := Collection(parent); ok { t.Fatal("collection leaked to parent") } if _, ok := Locale(parent); ok { t.Fatal("locale leaked to parent") } if got, ok := Collection(child); !ok || got != "col-child" { t.Fatalf("child Collection = (%q, %v)", got, ok) } } func TestNilContextAccessors(t *testing.T) { if _, ok := Actor(nil); ok { t.Fatal("Actor(nil) should be absent") } if _, ok := Organization(nil); ok { t.Fatal("Organization(nil) should be absent") } if _, ok := Collection(nil); ok { t.Fatal("Collection(nil) should be absent") } if _, ok := Locale(nil); ok { t.Fatal("Locale(nil) should be absent") } ctx := WithActor(nil, "user-1") if got, ok := Actor(ctx); !ok || got != "user-1" { t.Fatalf("WithActor(nil) = (%q, %v)", got, ok) } } func TestConcurrentContextAccessors(t *testing.T) { ctx := WithActor(context.Background(), "user-1") ctx = WithOrganization(ctx, "org-9") ctx = WithCollection(ctx, "col-3") ctx = WithLocale(ctx, "pl") var wg sync.WaitGroup for i := 0; i < 16; i++ { wg.Add(1) go func() { defer wg.Done() for j := 0; j < 80; j++ { if got, ok := Actor(ctx); !ok || got != "user-1" { t.Errorf("Actor = (%q, %v)", got, ok) return } if got, ok := Organization(ctx); !ok || got != "org-9" { t.Errorf("Organization = (%q, %v)", got, ok) return } if got, ok := Collection(ctx); !ok || got != "col-3" { t.Errorf("Collection = (%q, %v)", got, ok) return } if got, ok := Locale(ctx); !ok || got != "pl" { t.Errorf("Locale = (%q, %v)", got, ok) return } } }() } wg.Wait() } func TestNoPackageGlobalRequestState(t *testing.T) { entries, err := os.ReadDir(".") if err != nil { t.Fatal(err) } for _, entry := range entries { name := entry.Name() if entry.IsDir() || !strings.HasSuffix(name, ".go") || strings.HasSuffix(name, "_test.go") { continue } src, err := os.ReadFile(name) if err != nil { t.Fatal(err) } // Reject package-level request-state variables. Type keys are ok. for _, line := range strings.Split(string(src), "\n") { trim := strings.TrimSpace(line) if strings.HasPrefix(trim, "var ") && !strings.Contains(trim, "struct{}") && !strings.Contains(trim, "key") { if strings.Contains(strings.ToLower(trim), "actor") || strings.Contains(strings.ToLower(trim), "locale") || strings.Contains(strings.ToLower(trim), "organization") || strings.Contains(strings.ToLower(trim), "collection") { t.Fatalf("package-global request state in %s: %s", name, trim) } } } } }