Skip to content

Commit 519a461

Browse files
committed
test(mountsync): reproduce nested runtime tree traversal
1 parent 4415724 commit 519a461

1 file changed

Lines changed: 183 additions & 0 deletions

File tree

internal/mountsync/syncer_test.go

Lines changed: 183 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7488,6 +7488,76 @@ func (c *fakeClient) ListTree(ctx context.Context, workspaceID, path string, dep
74887488
}, nil
74897489
}
74907490

7491+
type hierarchicalTreeCall struct {
7492+
path string
7493+
depth int
7494+
entries int
7495+
}
7496+
7497+
// hierarchicalTreeClient mirrors the production tree contract: a request
7498+
// returns files and synthesized directories only through the requested depth.
7499+
// It lets traversal tests distinguish pruning before descent from filtering a
7500+
// flat depth=200 response after the server has already enumerated every leaf.
7501+
type hierarchicalTreeClient struct {
7502+
*fakeClient
7503+
calls []hierarchicalTreeCall
7504+
entriesReturned int
7505+
}
7506+
7507+
func (c *hierarchicalTreeClient) ListTree(_ context.Context, _ string, path string, depth int, cursor string) (TreeResponse, error) {
7508+
base := normalizeRemotePath(path)
7509+
if depth <= 0 {
7510+
depth = 1
7511+
}
7512+
entriesByPath := map[string]TreeEntry{}
7513+
for remotePath, file := range c.files {
7514+
remotePath = normalizeRemotePath(remotePath)
7515+
if !isUnderRemoteRoot(base, remotePath) || remotePath == base {
7516+
continue
7517+
}
7518+
relative := strings.TrimPrefix(strings.TrimPrefix(remotePath, base), "/")
7519+
parts := strings.Split(relative, "/")
7520+
levels := depth
7521+
if len(parts) < levels {
7522+
levels = len(parts)
7523+
}
7524+
for level := 1; level <= levels; level++ {
7525+
entryPath := normalizeRemotePath(strings.TrimSuffix(base, "/") + "/" + strings.Join(parts[:level], "/"))
7526+
if level == len(parts) {
7527+
entriesByPath[entryPath] = TreeEntry{
7528+
Path: entryPath,
7529+
Type: "file",
7530+
Revision: file.Revision,
7531+
ContentHash: file.ContentHash,
7532+
Size: int64(len(file.Content)),
7533+
}
7534+
continue
7535+
}
7536+
if _, exists := entriesByPath[entryPath]; !exists {
7537+
entriesByPath[entryPath] = TreeEntry{Path: entryPath, Type: "dir", Revision: "dir"}
7538+
}
7539+
}
7540+
}
7541+
entries := make([]TreeEntry, 0, len(entriesByPath))
7542+
for _, entry := range entriesByPath {
7543+
entries = append(entries, entry)
7544+
}
7545+
sort.Slice(entries, func(i, j int) bool { return entries[i].Path < entries[j].Path })
7546+
if cursor != "" {
7547+
start := len(entries)
7548+
for index, entry := range entries {
7549+
if entry.Path == cursor {
7550+
start = index + 1
7551+
break
7552+
}
7553+
}
7554+
entries = entries[start:]
7555+
}
7556+
c.calls = append(c.calls, hierarchicalTreeCall{path: base, depth: depth, entries: len(entries)})
7557+
c.entriesReturned += len(entries)
7558+
return TreeResponse{Path: base, Entries: entries}, nil
7559+
}
7560+
74917561
func (c *fakeClient) LatestEventID(ctx context.Context, workspaceID, provider string) (string, error) {
74927562
_ = ctx
74937563
_ = workspaceID
@@ -9943,6 +10013,119 @@ func TestPullRemoteFullTreeSkipsNestedMountRuntimeState(t *testing.T) {
994310013
}
994410014
}
994510015

10016+
func TestIsMountRuntimeRelativePathExcludesOnlyReservedRuntimeArtifacts(t *testing.T) {
10017+
tests := []struct {
10018+
path string
10019+
want bool
10020+
}{
10021+
{path: ".relay", want: true},
10022+
{path: "slack/channels/C123/messages/1/.relay/state.json", want: true},
10023+
{path: "slack/channels/C123/messages/1/.relay/outbox/acked/mountcmd_1.json", want: true},
10024+
{path: "slack/channels/C123/messages/1/.relayfile-mount-state.json", want: true},
10025+
{path: "slack/channels/C123/messages/1/relay/state.json", want: false},
10026+
{path: "slack/channels/C123/messages/1/.relay-notes/state.json", want: false},
10027+
{path: "slack/channels/C123/messages/1/outbox/acked/mountcmd_1.json", want: false},
10028+
{path: "slack/channels/C123/messages/1/notes/mountcmd_1.json", want: false},
10029+
{path: "slack/channels/C123/messages/1/.relayfile-mount-state.json.backup", want: false},
10030+
}
10031+
for _, tt := range tests {
10032+
t.Run(tt.path, func(t *testing.T) {
10033+
if got := isMountRuntimeRelativePath(tt.path); got != tt.want {
10034+
t.Fatalf("isMountRuntimeRelativePath(%q) = %t, want %t", tt.path, got, tt.want)
10035+
}
10036+
})
10037+
}
10038+
}
10039+
10040+
func TestPullRemoteFullTreePrunesNestedMountRuntimeBeforeDescendantEnumeration(t *testing.T) {
10041+
files := map[string]RemoteFile{
10042+
"/slack/channels/C123/messages/1780145510_376649.json": {
10043+
Path: "/slack/channels/C123/messages/1780145510_376649.json", Revision: "rev_msg", Content: `{"text":"hello"}`,
10044+
},
10045+
"/slack/channels/C123/messages/1780145510_376649/.relay-notes/keep.md": {
10046+
Path: "/slack/channels/C123/messages/1780145510_376649/.relay-notes/keep.md", Revision: "rev_notes", Content: "real notes",
10047+
},
10048+
"/slack/channels/C123/messages/1780145510_376649/outbox/acked/mountcmd_real.json": {
10049+
Path: "/slack/channels/C123/messages/1780145510_376649/outbox/acked/mountcmd_real.json", Revision: "rev_real_cmd", Content: `{"real":true}`,
10050+
},
10051+
"/slack/channels/C123/messages/1780145510_376649/.relay/state.json": {
10052+
Path: "/slack/channels/C123/messages/1780145510_376649/.relay/state.json", Revision: "rev_runtime_state", Content: `{"pendingWriteback":400}`,
10053+
},
10054+
}
10055+
for index := 0; index < 400; index++ {
10056+
path := fmt.Sprintf("/slack/channels/C123/messages/1780145510_376649/.relay/outbox/acked/mountcmd_%03d.json", index)
10057+
files[path] = RemoteFile{Path: path, Revision: fmt.Sprintf("rev_runtime_%03d", index), Content: `{"runtime":true}`}
10058+
}
10059+
baseClient := &fakeClient{files: files}
10060+
client := &hierarchicalTreeClient{fakeClient: baseClient}
10061+
logger := &captureLogger{}
10062+
localDir := t.TempDir()
10063+
syncer, err := NewSyncer(client, SyncerOptions{
10064+
WorkspaceID: "ws_prune_nested_relay",
10065+
RemoteRoot: "/slack",
10066+
LocalRoot: localDir,
10067+
Logger: logger,
10068+
})
10069+
if err != nil {
10070+
t.Fatalf("new syncer failed: %v", err)
10071+
}
10072+
runtimeDir := filepath.Join(localDir, "channels", "C123", "messages", "1780145510_376649", ".relay")
10073+
staleRuntimePath := filepath.Join(runtimeDir, "outbox", "acked", "mountcmd_stale.json")
10074+
if err := os.MkdirAll(filepath.Dir(staleRuntimePath), 0o755); err != nil {
10075+
t.Fatalf("mkdir stale runtime tree: %v", err)
10076+
}
10077+
if err := os.WriteFile(staleRuntimePath, []byte(`{"stale":true}`), 0o644); err != nil {
10078+
t.Fatalf("write stale runtime artifact: %v", err)
10079+
}
10080+
staleRemotePath := "/slack/channels/C123/messages/1780145510_376649/.relay/outbox/acked/mountcmd_stale.json"
10081+
syncer.state.Files[staleRemotePath] = trackedFile{Revision: "rev_stale", Hash: hashString(`{"stale":true}`)}
10082+
10083+
if err := syncer.pullRemoteFullTree(context.Background(), nil, bootstrapProgress{}); err != nil {
10084+
t.Fatalf("pullRemoteFullTree failed: %v", err)
10085+
}
10086+
10087+
if client.entriesReturned >= 40 {
10088+
t.Fatalf("runtime subtree was enumerated before filtering: returned %d entries across calls %#v", client.entriesReturned, client.calls)
10089+
}
10090+
for _, call := range client.calls {
10091+
if isMountRuntimeRemotePath(call.path) {
10092+
t.Fatalf("ListTree descended into reserved runtime subtree: %#v", call)
10093+
}
10094+
if call.depth != 1 {
10095+
t.Fatalf("ListTree depth = %d for %s, want 1 so runtime directories can be pruned before descent", call.depth, call.path)
10096+
}
10097+
}
10098+
for _, realPath := range []string{
10099+
filepath.Join(localDir, "channels", "C123", "messages", "1780145510_376649.json"),
10100+
filepath.Join(localDir, "channels", "C123", "messages", "1780145510_376649", ".relay-notes", "keep.md"),
10101+
filepath.Join(localDir, "channels", "C123", "messages", "1780145510_376649", "outbox", "acked", "mountcmd_real.json"),
10102+
} {
10103+
if _, err := os.Stat(realPath); err != nil {
10104+
t.Fatalf("expected real workspace content %s to remain mirrored: %v", realPath, err)
10105+
}
10106+
}
10107+
if _, err := os.Stat(runtimeDir); !errors.Is(err, os.ErrNotExist) {
10108+
t.Fatalf("nested runtime subtree should be absent after pruning, stat err=%v", err)
10109+
}
10110+
if _, exists := syncer.state.Files[staleRemotePath]; exists {
10111+
t.Fatalf("stale nested runtime artifact remained tracked")
10112+
}
10113+
for path, calls := range baseClient.readFileCallsByPath {
10114+
if isMountRuntimeRemotePath(path) && calls > 0 {
10115+
t.Fatalf("runtime artifact %s was read %d time(s)", path, calls)
10116+
}
10117+
}
10118+
var summary string
10119+
for _, line := range logger.lines {
10120+
if strings.Contains(line, "mount full-tree traversal summary") {
10121+
summary = line
10122+
}
10123+
}
10124+
if !strings.Contains(summary, "runtime_subtrees_pruned=1") {
10125+
t.Fatalf("expected traversal summary to report one pruned runtime subtree, got %q", summary)
10126+
}
10127+
}
10128+
994610129
func TestPullRemoteIncrementalSkipsNestedMountRuntimeState(t *testing.T) {
994710130
client := &fakeClient{
994810131
files: map[string]RemoteFile{

0 commit comments

Comments
 (0)