Files
namespace/orgproject_test.go
zeekayandHanzo Dev 555c41b82e A namespace naming a database, and that name becoming a location
Naming the database and rendering that name as a place were one primitive
living in two repositories: hanzoai/cloud held the slugger, the layout and
the resolver, and this package held only the value they produced. A fact with
two homes is a fact that can disagree with itself, and these particular strings
are directory names on live volumes and keys in live buckets — a disagreement
does not fail, it opens an empty database beside a real one.

So the whole primitive moves here and cloud imports it:

  Sanitize     one injective slug for a name a person chose
  OrgProject   the door those names walk through to become a namespace
  MustOrgProject
  Key          a namespace rendered as the place one subsystem's file lives
  Path         Key resolved against a data directory

Sanitize refuses a name carrying an invisible rune (nothing downstream can be
trusted not to trim it) and disambiguates every other fold with a hash of the
raw bytes, so two spellings are two files. OrgProject is named for what it
takes; Of already means something else here — the namespace a billing subject
owns — and clobbering it would have been the same collapse in miniature.

Every golden value in the tests was captured from the implementation that
wrote the files already on disk, before the code moved, so the rendering is
byte-identical and no live data is orphaned.

Co-authored-by: Hanzo Dev <[email protected]>
2026-08-01 18:14:05 -07:00

117 lines
4.1 KiB
Go

// Copyright (C) 2026, Hanzo AI. All rights reserved.
// See the file LICENSE for licensing terms.
package namespace
import (
"path"
"strings"
"testing"
)
// TestOrgProjectRefuses pins the door shut on the names that must never become
// a database. Each of these would, if admitted, be a file two distinct entities
// could share or a segment that escapes the data directory.
func TestOrgProjectRefuses(t *testing.T) {
for _, tc := range []struct{ org, project, why string }{
{"", "", "an empty org names no entity"},
{"acme ", "", "a trailing space folds onto acme after any trim"},
{"acme\u200b", "", "a zero-width rune is invisible in an identifier"},
{"acme", " ", "a whitespace-only project names no group"},
{"!!!", "", "an org that folds to nothing would be named by its hash alone"},
{"-abc", "", "a slug starting with '-' is not a legal segment"},
{"acme", "!!!", "a project that folds to nothing would be named by its hash alone"},
} {
if ns, err := OrgProject(tc.org, tc.project); err == nil {
t.Errorf("OrgProject(%q, %q) = %q, want an error: %s", tc.org, tc.project, ns, tc.why)
}
}
}
// TestOrgProjectNeutralisesTraversal pins what happens to a name that looks
// like a path. It is not refused — Sanitize folds every separator and dot to
// "-" and then disambiguates with a hash of the raw name, so "../etc" becomes
// one safe segment that is still distinct from an org actually called "etc".
// The property that matters is not "rejected" but "cannot leave its directory",
// and asserting the wrong one of those would be a test that passes for the
// wrong reason.
func TestOrgProjectNeutralisesTraversal(t *testing.T) {
hostile := []string{"../etc", "..", ".", "a/b", "..%2f..%2fvictim", `a\b`}
for _, org := range hostile {
ns, err := OrgProject(org, "")
if err != nil {
continue // refused outright is also fine
}
id := ns.ID()
if id == "." || id == ".." || strings.ContainsAny(id, `/\`) {
t.Errorf("OrgProject(%q) = %q, whose id %q can leave its directory", org, ns, id)
}
key, err := Key(ns, "widget")
if err != nil {
t.Fatalf("Key(%q): %v", ns, err)
}
if want := path.Join(orgsRoot, id, "widget.db"); key != want {
t.Errorf("OrgProject(%q) rendered to %q, want %q", org, key, want)
}
}
for _, project := range hostile {
ns, err := OrgProject("acme", project)
if err != nil {
continue
}
if g := ns.Group().String(); g == "." || g == ".." || strings.ContainsAny(g, `/\`) {
t.Errorf("project %q became group %q, which can leave its directory", project, g)
}
if ns.ID() != "acme" {
t.Errorf("project %q changed the org to %q", project, ns.ID())
}
}
}
// TestOrgProjectIsInjective proves the property the whole boundary rests on:
// two distinct orgs never name one database. Sanitize carries it, and Org's own
// case fold cannot undo it, because Sanitize has already lowercased everything
// it emits.
func TestOrgProjectIsInjective(t *testing.T) {
seen := map[string]string{}
for _, org := range []string{
"acme", "ACME", "Acme", "acme-corp", "acme_corp", "acme.corp",
"team-a", "team.a", "team_a", "platform", "system", "org",
} {
ns, err := OrgProject(org, "")
if err != nil {
t.Fatalf("OrgProject(%q): %v", org, err)
}
if prev, dup := seen[ns.String()]; dup {
t.Fatalf("orgs %q and %q name the SAME database %q", prev, org, ns)
}
seen[ns.String()] = org
}
// And no org can reach the deployment's own partition, whatever it is called.
for _, org := range []string{"_platform", "platform", "system", "_system"} {
ns, err := OrgProject(org, "")
if err != nil {
continue
}
if ns == System() {
t.Fatalf("org %q named the system partition", org)
}
if k, err := Key(ns, "kms"); err == nil {
if p, _ := Key(System(), "kms"); k == p {
t.Fatalf("org %q rendered to the system partition's file %q", org, k)
}
}
}
}
// TestMustOrgProjectPanics states what the Must form is for: a name fixed in
// the source, wrong before the program runs.
func TestMustOrgProjectPanics(t *testing.T) {
defer func() {
if recover() == nil {
t.Fatal("MustOrgProject on an invalid org must panic")
}
}()
MustOrgProject("", "")
}