test(apps,fs): update availability + listing tests for flat-peer layout
availability_test: tools resolve via the peer cascade (classifier on incoming/working/staging, transmittal on staging, tables on mdl/rsk/ssr). tree_test: drop the abandoned per-user-home + folder-nav virtual tests; add an mdl/ cross-party aggregate-listing test; repoint empty-when-missing to the declared peers.
This commit is contained in:
parent
7d462ab7a4
commit
150da9d186
3 changed files with 87 additions and 246 deletions
|
|
@ -11,48 +11,50 @@ func TestAppAvailableAt(t *testing.T) {
|
|||
dir, app string
|
||||
want bool
|
||||
}{
|
||||
// archive: everywhere
|
||||
// archive: everywhere (baseline)
|
||||
{root, "archive", true},
|
||||
{root + "/Project-A", "archive", true},
|
||||
{root + "/Project-A/working", "archive", true},
|
||||
{root + "/Project-A/working/ACME", "archive", true},
|
||||
{root + "/Project-A/some-other-folder", "archive", true},
|
||||
|
||||
// landing: only at root
|
||||
{root, "landing", true},
|
||||
{root + "/Project-A", "landing", false},
|
||||
|
||||
// classifier: per-party working/, staging/, incoming/ subtrees
|
||||
// classifier: the inbound/draft workspace peers (incoming/working/staging)
|
||||
{root, "classifier", false},
|
||||
{root + "/Project-A", "classifier", false},
|
||||
{root + "/Project-A/archive/ACME/working", "classifier", true},
|
||||
{root + "/Project-A/archive/ACME/working/deep/nested/path", "classifier", true},
|
||||
{root + "/Project-A/archive/ACME/staging", "classifier", true},
|
||||
{root + "/Project-A/archive/ACME/staging/2026-06-15_x (DFT) - y", "classifier", true},
|
||||
{root + "/Project-A/archive/ACME/incoming", "classifier", true},
|
||||
{root + "/Project-A/archive/ACME/incoming/sub", "classifier", true},
|
||||
{root + "/Project-A/working/ACME", "classifier", true},
|
||||
{root + "/Project-A/working/ACME/deep/nested/path", "classifier", true},
|
||||
{root + "/Project-A/staging/ACME", "classifier", true},
|
||||
{root + "/Project-A/staging/ACME/2026-06-15_x (DFT) - y", "classifier", true},
|
||||
{root + "/Project-A/incoming/ACME", "classifier", true},
|
||||
{root + "/Project-A/incoming/ACME/sub", "classifier", true},
|
||||
{root + "/Project-A/reviewing/ACME", "classifier", false},
|
||||
{root + "/Project-A/archive/ACME/received", "classifier", false},
|
||||
{root + "/Project-A/archive/ACME/issued", "classifier", false},
|
||||
{root + "/Project-A/archive/ACME/mdl", "classifier", false},
|
||||
{root + "/Project-A/mdl/ACME", "classifier", false},
|
||||
{root + "/Project-A/some-other-folder", "classifier", false},
|
||||
|
||||
// browse: universal — every directory has browse available
|
||||
// (it's in the embedded-defaults baseline available_tools).
|
||||
{root + "/Project-A/archive/ACME/working", "browse", true},
|
||||
{root + "/Project-A/archive/ACME/working/sub", "browse", true},
|
||||
{root + "/Project-A/archive/ACME/staging", "browse", true},
|
||||
{root + "/Project-A/archive/ACME/incoming", "browse", true},
|
||||
// browse: universal (baseline)
|
||||
{root + "/Project-A/working/ACME", "browse", true},
|
||||
{root + "/Project-A/staging/ACME", "browse", true},
|
||||
{root + "/Project-A/incoming/ACME", "browse", true},
|
||||
|
||||
// transmittal: per-party staging/ only
|
||||
{root + "/Project-A/archive/ACME/staging", "transmittal", true},
|
||||
{root + "/Project-A/archive/ACME/staging/sub", "transmittal", true},
|
||||
{root + "/Project-A/archive/ACME/working", "transmittal", false},
|
||||
// transmittal: the staging peer only
|
||||
{root + "/Project-A/staging/ACME", "transmittal", true},
|
||||
{root + "/Project-A/staging/ACME/sub", "transmittal", true},
|
||||
{root + "/Project-A/working/ACME", "transmittal", false},
|
||||
{root + "/Project-A/archive/ACME/issued", "transmittal", false},
|
||||
|
||||
// case-fold: any case of canonical names matches
|
||||
{root + "/Project-A/archive/ACME/Staging", "transmittal", true},
|
||||
{root + "/Project-A/archive/ACME/STAGING", "transmittal", true},
|
||||
{root + "/Project-A/archive/ACME/Incoming", "classifier", true},
|
||||
{root + "/Project-A/Archive/ACME/incoming", "classifier", true},
|
||||
// tables: the register peers
|
||||
{root + "/Project-A/mdl/ACME", "tables", true},
|
||||
{root + "/Project-A/rsk/ACME", "tables", true},
|
||||
{root + "/Project-A/ssr", "tables", true},
|
||||
|
||||
// case-fold: any case of a peer name matches
|
||||
{root + "/Project-A/Staging/ACME", "transmittal", true},
|
||||
{root + "/Project-A/STAGING/ACME", "transmittal", true},
|
||||
{root + "/Project-A/Incoming/ACME", "classifier", true},
|
||||
|
||||
// unknown app
|
||||
{root + "/Project-A", "weird", false},
|
||||
|
|
@ -73,45 +75,36 @@ func TestDefaultAppAt(t *testing.T) {
|
|||
dir string
|
||||
want string
|
||||
}{
|
||||
// At the deployment root itself, no default tool — landing handles
|
||||
// the project picker via a separate path.
|
||||
// Deployment root + bare project root: no default tool.
|
||||
{root, ""},
|
||||
// Bare project root: no default. Trailing-slash URL serves browse;
|
||||
// no-slash falls through to the redirect.
|
||||
{root + "/Project-A", ""},
|
||||
// Project-level virtual aggregators (sibling to archive/).
|
||||
// Top-level peers.
|
||||
{root + "/Project-A/working", "browse"},
|
||||
{root + "/Project-A/staging", "browse"},
|
||||
{root + "/Project-A/staging", "transmittal"},
|
||||
{root + "/Project-A/reviewing", "browse"},
|
||||
{root + "/Project-A/incoming", "classifier"},
|
||||
{root + "/Project-A/ssr", "tables"},
|
||||
{root + "/Project-A/mdl", "tables"},
|
||||
{root + "/Project-A/rsk", "tables"},
|
||||
// Per-party lifecycle slots (the real physical homes).
|
||||
{root + "/Project-A/archive/Acme/working", "browse"},
|
||||
{root + "/Project-A/archive/Acme/working/alice@example.com", "browse"},
|
||||
{root + "/Project-A/archive/Acme/working/2026-06-15_x (DFT) - y", "browse"},
|
||||
{root + "/Project-A/archive/Acme/staging", "transmittal"},
|
||||
{root + "/Project-A/archive/Acme/staging/2026-06-15_x (DFT) - y", "transmittal"},
|
||||
{root + "/Project-A/archive/Acme/reviewing", "browse"},
|
||||
// archive: at the archive root, party folders default to archive.
|
||||
// Per-party subfolders override per their function:
|
||||
// incoming → classifier (the bulk-rename workflow)
|
||||
// received / issued → archive (WORM record browser)
|
||||
// Per-party subdirs inherit the peer default.
|
||||
{root + "/Project-A/working/Acme", "browse"},
|
||||
{root + "/Project-A/working/Acme/2026-06-15_x (DFT) - y", "browse"},
|
||||
{root + "/Project-A/staging/Acme", "transmittal"},
|
||||
{root + "/Project-A/incoming/Acme", "classifier"},
|
||||
{root + "/Project-A/mdl/Acme", "tables"},
|
||||
{root + "/Project-A/mdl/Acme/anything-deeper", "tables"},
|
||||
{root + "/Project-A/rsk/Acme", "tables"},
|
||||
// The committed record: archive subtree → archive tool.
|
||||
{root + "/Project-A/archive", "archive"},
|
||||
{root + "/Project-A/archive/Acme", "archive"},
|
||||
{root + "/Project-A/archive/Acme/incoming", "classifier"},
|
||||
{root + "/Project-A/archive/Acme/issued", "archive"},
|
||||
{root + "/Project-A/archive/Acme/received", "archive"},
|
||||
// mdl/rsk win over the broader archive rule.
|
||||
{root + "/Project-A/archive/Acme/mdl", "tables"},
|
||||
{root + "/Project-A/archive/Acme/mdl/anything-deeper", "tables"},
|
||||
{root + "/Project-A/archive/Acme/rsk", "tables"},
|
||||
{root + "/Project-A/archive/Acme/issued", "archive"},
|
||||
// Random non-canonical folder names → no default.
|
||||
{root + "/Project-A/scratch", ""},
|
||||
// Case-fold on canonical names.
|
||||
{root + "/Project-A/archive/Acme/Working", "browse"},
|
||||
{root + "/Project-A/archive/Acme/STAGING", "transmittal"},
|
||||
{root + "/Project-A/Archive/Acme/MDL", "tables"},
|
||||
// Case-fold on peer names.
|
||||
{root + "/Project-A/Working/Acme", "browse"},
|
||||
{root + "/Project-A/STAGING/Acme", "transmittal"},
|
||||
{root + "/Project-A/MDL/Acme", "tables"},
|
||||
}
|
||||
for _, tc := range cases {
|
||||
t.Run(tc.dir, func(t *testing.T) {
|
||||
|
|
|
|||
|
|
@ -66,8 +66,8 @@ func ListDirectory(ctx context.Context, decider policy.Decider, fsRoot, dirPath,
|
|||
// written into them — but the cascade (defaults.zddc.yaml
|
||||
// plus any on-disk overrides) declares them via paths:, so
|
||||
// the stage-strip / file nav can link unconditionally.
|
||||
// Returning [] gives a usable empty view; the
|
||||
// virtualUserHomeEntry below still fires for working/.
|
||||
// Returning [] gives a usable empty view (the tables peers
|
||||
// still surface their synthetic spec entries below).
|
||||
if os.IsNotExist(err) && zddc.IsDeclaredPath(fsRoot, absDir) {
|
||||
entries = nil
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -21,157 +21,13 @@ func setupTreeRoot(t *testing.T) string {
|
|||
return root
|
||||
}
|
||||
|
||||
// Per-user homes now live at archive/<party>/working/<email>/ (depth-
|
||||
// 4). The virtual entry fires when listing that path for a viewer
|
||||
// whose home doesn't yet exist on disk.
|
||||
|
||||
func TestListDirectory_VirtualUserHome_AppearsWhenMissing(t *testing.T) {
|
||||
// Listing a cascade-declared peer that doesn't exist on disk yet returns
|
||||
// an empty slice instead of os.ErrNotExist, so fresh projects don't 404.
|
||||
// (The tables peers mdl/rsk/ssr instead surface their synthetic spec
|
||||
// entries; see TestListDirectory_MdlAggregate.)
|
||||
func TestListDirectory_DeclaredPeerEmptyWhenMissing(t *testing.T) {
|
||||
root := setupTreeRoot(t)
|
||||
if err := os.MkdirAll(filepath.Join(root, "Proj", "archive", "Acme", "working"), 0o755); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
zddc.InvalidateCache(root)
|
||||
|
||||
got, err := ListDirectory(context.Background(), nil, root,
|
||||
"Proj/archive/Acme/working", "alice@example.com",
|
||||
"/Proj/archive/Acme/working/", false, false)
|
||||
if err != nil {
|
||||
t.Fatalf("list: %v", err)
|
||||
}
|
||||
|
||||
var virtual *string
|
||||
for i := range got {
|
||||
if got[i].Virtual {
|
||||
n := got[i].Name
|
||||
virtual = &n
|
||||
}
|
||||
}
|
||||
if virtual == nil {
|
||||
t.Fatalf("expected synthetic <viewer-email>/ entry, got entries: %+v", got)
|
||||
}
|
||||
if *virtual != "alice@example.com/" {
|
||||
t.Errorf("synthetic name = %q, want alice@example.com/", *virtual)
|
||||
}
|
||||
}
|
||||
|
||||
func TestListDirectory_VirtualUserHome_SuppressedWhenRealExists(t *testing.T) {
|
||||
root := setupTreeRoot(t)
|
||||
// A real folder exists for the viewer (any case).
|
||||
if err := os.MkdirAll(filepath.Join(root, "Proj", "archive", "Acme", "working", "Alice@Example.com"), 0o755); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
zddc.InvalidateCache(root)
|
||||
|
||||
got, err := ListDirectory(context.Background(), nil, root,
|
||||
"Proj/archive/Acme/working", "alice@example.com",
|
||||
"/Proj/archive/Acme/working/", false, false)
|
||||
if err != nil {
|
||||
t.Fatalf("list: %v", err)
|
||||
}
|
||||
for _, fi := range got {
|
||||
if fi.Virtual {
|
||||
t.Errorf("synthetic entry should be suppressed when a case-fold match exists; got %+v", fi)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestListDirectory_VirtualUserHome_AnonymousNoEntry(t *testing.T) {
|
||||
root := setupTreeRoot(t)
|
||||
if err := os.MkdirAll(filepath.Join(root, "Proj", "archive", "Acme", "working"), 0o755); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
zddc.InvalidateCache(root)
|
||||
|
||||
got, err := ListDirectory(context.Background(), nil, root,
|
||||
"Proj/archive/Acme/working", "" /* no viewer */,
|
||||
"/Proj/archive/Acme/working/", false, false)
|
||||
if err != nil {
|
||||
t.Fatalf("list: %v", err)
|
||||
}
|
||||
for _, fi := range got {
|
||||
if fi.Virtual {
|
||||
t.Errorf("anonymous viewer should not see synthetic entries; got %+v", fi)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestListDirectory_VirtualUserHome_OutsideWorkingNoEntry(t *testing.T) {
|
||||
root := setupTreeRoot(t)
|
||||
if err := os.MkdirAll(filepath.Join(root, "Proj", "archive", "Acme", "staging"), 0o755); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
zddc.InvalidateCache(root)
|
||||
|
||||
got, err := ListDirectory(context.Background(), nil, root,
|
||||
"Proj/archive/Acme/staging", "alice@example.com",
|
||||
"/Proj/archive/Acme/staging/", false, false)
|
||||
if err != nil {
|
||||
t.Fatalf("list: %v", err)
|
||||
}
|
||||
for _, fi := range got {
|
||||
if fi.Virtual {
|
||||
t.Errorf("staging/ should not have a synthetic user-home entry; got %+v", fi)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestListDirectory_VirtualUserHome_DeepWorkingNoEntry(t *testing.T) {
|
||||
root := setupTreeRoot(t)
|
||||
// Listing inside working/<email>/ — no synthetic entry should fire.
|
||||
if err := os.MkdirAll(filepath.Join(root, "Proj", "archive", "Acme", "working", "alice@example.com"), 0o755); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
zddc.InvalidateCache(root)
|
||||
|
||||
got, err := ListDirectory(context.Background(), nil, root,
|
||||
"Proj/archive/Acme/working/alice@example.com", "alice@example.com",
|
||||
"/Proj/archive/Acme/working/alice@example.com/", false, false)
|
||||
if err != nil {
|
||||
t.Fatalf("list: %v", err)
|
||||
}
|
||||
for _, fi := range got {
|
||||
if fi.Virtual {
|
||||
t.Errorf("nested working/ subdir must not synthesise the user home; got %+v", fi)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestListDirectory_VirtualUserHome_CaseFoldWorking(t *testing.T) {
|
||||
root := setupTreeRoot(t)
|
||||
// Pre-existing PascalCase Working/ under archive/<party>/.
|
||||
if err := os.MkdirAll(filepath.Join(root, "Proj", "archive", "Acme", "Working"), 0o755); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
zddc.InvalidateCache(root)
|
||||
|
||||
got, err := ListDirectory(context.Background(), nil, root,
|
||||
"Proj/archive/Acme/Working", "alice@example.com",
|
||||
"/Proj/archive/Acme/Working/", false, false)
|
||||
if err != nil {
|
||||
t.Fatalf("list: %v", err)
|
||||
}
|
||||
var found bool
|
||||
for _, fi := range got {
|
||||
if fi.Virtual {
|
||||
found = true
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
t.Errorf("PascalCase Working/ should still surface the synthetic entry; got entries: %+v", got)
|
||||
}
|
||||
}
|
||||
|
||||
// Listing a canonical-folder path that doesn't exist on disk yet
|
||||
// returns an empty slice instead of os.ErrNotExist. The stage-strip
|
||||
// nav links into <project>/archive/ etc. unconditionally; this keeps
|
||||
// fresh projects from 404'ing.
|
||||
//
|
||||
// The synthetic per-user home entry fires for the in-party working
|
||||
// slot; other canonical slots return a plain empty listing.
|
||||
func TestListDirectory_CanonicalProjectFolder_EmptyWhenMissing(t *testing.T) {
|
||||
root := setupTreeRoot(t)
|
||||
// Proj exists; the party folder skeleton does not.
|
||||
if err := os.MkdirAll(filepath.Join(root, "Proj", "archive", "Acme"), 0o755); err != nil {
|
||||
if err := os.MkdirAll(filepath.Join(root, "Proj"), 0o755); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := os.WriteFile(filepath.Join(root, "Proj", ".zddc"),
|
||||
|
|
@ -180,25 +36,16 @@ func TestListDirectory_CanonicalProjectFolder_EmptyWhenMissing(t *testing.T) {
|
|||
}
|
||||
zddc.InvalidateCache(root)
|
||||
|
||||
for _, stage := range []string{"working", "staging", "reviewing", "incoming"} {
|
||||
dirPath := "Proj/archive/Acme/" + stage
|
||||
baseURL := "/" + dirPath + "/"
|
||||
for _, peer := range []string{"working", "staging", "reviewing", "incoming"} {
|
||||
dirPath := "Proj/" + peer
|
||||
got, err := ListDirectory(context.Background(), nil, root,
|
||||
dirPath, "alice@example.com", baseURL, false, false)
|
||||
dirPath, "alice@example.com", "/"+dirPath+"/", false, false)
|
||||
if err != nil {
|
||||
t.Errorf("ListDirectory(%s) on missing dir: err = %v, want nil", dirPath, err)
|
||||
t.Errorf("ListDirectory(%s) on missing peer: err = %v, want nil", dirPath, err)
|
||||
continue
|
||||
}
|
||||
// working/ surfaces a synthetic <viewer-email>/ entry; the
|
||||
// others should be a flat empty listing.
|
||||
if stage == "working" {
|
||||
if len(got) != 1 || !got[0].Virtual {
|
||||
t.Errorf("ListDirectory(%s) on missing dir: got %+v, want only the virtual home entry", dirPath, got)
|
||||
}
|
||||
} else {
|
||||
if len(got) != 0 {
|
||||
t.Errorf("ListDirectory(%s) on missing dir: got %+v, want empty", dirPath, got)
|
||||
}
|
||||
t.Errorf("ListDirectory(%s) on missing peer: got %+v, want empty", dirPath, got)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -224,37 +71,44 @@ func TestListDirectory_NonCanonicalMissing_StillNotFound(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
// Project-level folder-nav virtual lists only the parties that have
|
||||
// non-empty content in the slot. Empty/missing party slots are
|
||||
// filtered out.
|
||||
func TestListDirectory_VirtualFolderNav_FiltersInFlight(t *testing.T) {
|
||||
// The mdl/ peer root renders the cross-party AGGREGATE: one entry per
|
||||
// physical mdl/<party>/*.yaml (real URLs), not a folder-nav of party
|
||||
// dirs. Spec entries (table.yaml/form.yaml) are advertised too.
|
||||
func TestListDirectory_MdlAggregate(t *testing.T) {
|
||||
root := setupTreeRoot(t)
|
||||
// Acme has a populated working/; Beta is scaffolded but empty.
|
||||
if err := os.MkdirAll(filepath.Join(root, "Proj", "archive", "Acme", "working"), 0o755); err != nil {
|
||||
mk := func(p string) {
|
||||
if err := os.MkdirAll(filepath.Dir(p), 0o755); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := os.WriteFile(filepath.Join(root, "Proj", "archive", "Acme", "working", "draft.md"), []byte("x"), 0o644); err != nil {
|
||||
if err := os.WriteFile(p, []byte("id: x\n"), 0o644); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := os.MkdirAll(filepath.Join(root, "Proj", "archive", "Beta", "working"), 0o755); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
mk(filepath.Join(root, "Proj", "mdl", "Acme", "D-001.yaml"))
|
||||
mk(filepath.Join(root, "Proj", "mdl", "Beta", "D-009.yaml"))
|
||||
zddc.InvalidateCache(root)
|
||||
|
||||
got, err := ListDirectory(context.Background(), nil, root,
|
||||
"Proj/working", "alice@example.com", "/Proj/working/", false, false)
|
||||
"Proj/mdl", "alice@example.com", "/Proj/mdl/", false, false)
|
||||
if err != nil {
|
||||
t.Fatalf("list: %v", err)
|
||||
}
|
||||
var partyDirs []string
|
||||
rowURLs := map[string]bool{}
|
||||
for _, fi := range got {
|
||||
if fi.IsDir && fi.Virtual {
|
||||
partyDirs = append(partyDirs, fi.Name)
|
||||
if !fi.IsDir {
|
||||
rowURLs[fi.URL] = true
|
||||
}
|
||||
}
|
||||
want := []string{"Acme/"}
|
||||
if len(partyDirs) != 1 || partyDirs[0] != want[0] {
|
||||
t.Errorf("project-level folder-nav listing = %v, want %v", partyDirs, want)
|
||||
for _, want := range []string{"/Proj/mdl/Acme/D-001.yaml", "/Proj/mdl/Beta/D-009.yaml"} {
|
||||
if !rowURLs[want] {
|
||||
t.Errorf("aggregate listing missing row %q; got %+v", want, got)
|
||||
}
|
||||
}
|
||||
// No party SUBDIR entries in the aggregate (rows, not folders).
|
||||
for _, fi := range got {
|
||||
if fi.IsDir {
|
||||
t.Errorf("aggregate mdl/ should not list party dirs; got dir %q", fi.Name)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -301,9 +155,6 @@ func TestListDirectory_VerbsPerEntry(t *testing.T) {
|
|||
if fi.Verbs != want {
|
||||
t.Errorf("entry %s verbs = %q, want %q", fi.Name, fi.Verbs, want)
|
||||
}
|
||||
// Writable stays in lockstep with verbs for the transition
|
||||
// window — w bit for files, r/c semantics for dirs (no
|
||||
// Writable on dirs today; we don't assert it).
|
||||
if !fi.IsDir {
|
||||
wantWritable := want == "rw"
|
||||
if fi.Writable != wantWritable {
|
||||
|
|
@ -330,7 +181,6 @@ func TestListDirectory_VerbsActiveAdminBypass(t *testing.T) {
|
|||
}
|
||||
zddc.InvalidateCache(root)
|
||||
|
||||
// Elevated admin sees rwcda everywhere.
|
||||
got, err := ListDirectory(context.Background(), nil, root,
|
||||
"Proj", "admin@example.com", "/Proj/", false, true /* elevated */)
|
||||
if err != nil {
|
||||
|
|
@ -342,8 +192,6 @@ func TestListDirectory_VerbsActiveAdminBypass(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
// Same admin un-elevated sees nothing (no explicit ACL grant,
|
||||
// admin bypass disabled).
|
||||
got, err = ListDirectory(context.Background(), nil, root,
|
||||
"Proj", "admin@example.com", "/Proj/", false, false)
|
||||
if err != nil {
|
||||
|
|
|
|||
Loading…
Reference in a new issue