// Copyright (c) 2026 Lark Technologies Pte. Ltd. // SPDX-License-Identifier: MIT package drive import ( "crypto/sha256" "encoding/hex" "encoding/json" "errors" "net/http" "os" "path" "path/filepath" "strconv" "strings" "testing" "time" "github.com/larksuite/cli/errs" "github.com/larksuite/cli/internal/cmdutil" "github.com/larksuite/cli/internal/httpmock" ) const ( duplicateRemoteFileIDFirst = "example-file-token-first" duplicateRemoteFileIDSecond = "example-file-token-second" duplicateRemoteFileIDThird = "example-file-token-third" duplicateRemoteFolderID = "example-folder-token" ) func TestDriveStatusFailsOnDuplicateRemoteFiles(t *testing.T) { f, stdout, _, reg := cmdutil.TestFactory(t, driveTestConfig()) tmpDir := t.TempDir() withDriveWorkingDir(t, tmpDir) if err := os.MkdirAll("local", 0o755); err != nil { t.Fatalf("MkdirAll: %v", err) } registerDuplicateRemoteFiles(reg) err := mountAndRunDrive(t, DriveStatus, []string{ "+status", "--local-dir", "local", "--folder-token", "folder_root", "--as", "bot", }, f, stdout) assertDuplicateRemotePathError(t, err, "dup.txt", duplicateRemoteFileIDFirst, duplicateRemoteFileIDSecond) if stdout.String() != "" { t.Fatalf("stdout should be empty on duplicate_remote_path, got: %s", stdout.String()) } reg.Verify(t) } func TestDrivePullFailsOnDuplicateRemoteFilesBeforeWriting(t *testing.T) { f, stdout, _, reg := cmdutil.TestFactory(t, driveTestConfig()) tmpDir := t.TempDir() withDriveWorkingDir(t, tmpDir) if err := os.MkdirAll("local", 0o755); err != nil { t.Fatalf("MkdirAll: %v", err) } registerDuplicateRemoteFiles(reg) err := mountAndRunDrive(t, DrivePull, []string{ "+pull", "--local-dir", "local", "--folder-token", "folder_root", "--as", "bot", }, f, stdout) assertDuplicateRemotePathError(t, err, "dup.txt", duplicateRemoteFileIDFirst, duplicateRemoteFileIDSecond) if _, statErr := os.Stat(filepath.Join("local", "dup.txt")); !os.IsNotExist(statErr) { t.Fatalf("duplicate default failure must not write local dup.txt; stat err=%v", statErr) } if stdout.String() != "" { t.Fatalf("stdout should be empty on duplicate_remote_path, got: %s", stdout.String()) } reg.Verify(t) } func TestDrivePullRenameDownloadsDuplicateRemoteFilesWithStableHashSuffix(t *testing.T) { f, stdout, _, reg := cmdutil.TestFactory(t, driveTestConfig()) tmpDir := t.TempDir() withDriveWorkingDir(t, tmpDir) if err := os.MkdirAll("local", 0o755); err != nil { t.Fatalf("MkdirAll: %v", err) } registerDuplicateRemoteFiles(reg) reg.Register(&httpmock.Stub{ Method: "GET", URL: "/open-apis/drive/v1/files/" + duplicateRemoteFileIDFirst + "/download", Status: 200, Body: []byte("FIRST"), Headers: http.Header{"Content-Type": []string{"application/octet-stream"}}, }) reg.Register(&httpmock.Stub{ Method: "GET", URL: "/open-apis/drive/v1/files/" + duplicateRemoteFileIDSecond + "/download", Status: 200, Body: []byte("SECOND"), Headers: http.Header{"Content-Type": []string{"application/octet-stream"}}, }) err := mountAndRunDrive(t, DrivePull, []string{ "+pull", "--local-dir", "local", "--folder-token", "folder_root", "--on-duplicate-remote", "rename", "--as", "bot", }, f, stdout) if err != nil { t.Fatalf("unexpected error: %v\nstdout: %s", err, stdout.String()) } renamedRelPath := expectedRenamedRelPath("dup.txt", duplicateRemoteFileIDSecond, 12, 0) mustReadFile(t, filepath.Join("local", "dup.txt"), "FIRST") mustReadFile(t, filepath.Join("local", renamedRelPath), "SECOND") if strings.Contains(renamedRelPath, duplicateRemoteFileIDSecond) { t.Fatalf("renamed rel_path should not expose raw file token: %s", renamedRelPath) } payload := decodeDrivePullStdout(t, stdout.Bytes()) if got := payload.Data.Summary.Downloaded; got != 2 { t.Fatalf("summary.downloaded = %d, want 2", got) } if item := findPullItem(payload.Data.Items, renamedRelPath); item.SourceID == "" || item.FileToken != "" { t.Fatalf("rename item should emit source_id without file_token, got: %#v", item) } assertPullItemAction(t, stdout.Bytes(), renamedRelPath, "downloaded") reg.Verify(t) } func TestDrivePullRenameStrengthensSuffixWhenShortHashTargetAlreadyExists(t *testing.T) { f, stdout, _, reg := cmdutil.TestFactory(t, driveTestConfig()) tmpDir := t.TempDir() withDriveWorkingDir(t, tmpDir) if err := os.MkdirAll("local", 0o755); err != nil { t.Fatalf("MkdirAll: %v", err) } shortHashRelPath := expectedRenamedRelPath("dup.txt", duplicateRemoteFileIDSecond, 12, 0) registerRemoteListing(reg, "folder_root", []map[string]interface{}{ {"token": duplicateRemoteFileIDFirst, "name": "dup.txt", "type": "file", "size": 5, "created_time": "1", "modified_time": "1"}, {"token": duplicateRemoteFileIDSecond, "name": "dup.txt", "type": "file", "size": 6, "created_time": "2", "modified_time": "2"}, {"token": duplicateRemoteFileIDThird, "name": shortHashRelPath, "type": "file", "size": 7, "created_time": "3", "modified_time": "3"}, }) registerDownload(reg, duplicateRemoteFileIDFirst, "FIRST") registerDownload(reg, duplicateRemoteFileIDSecond, "SECOND") registerDownload(reg, duplicateRemoteFileIDThird, "THIRD") err := mountAndRunDrive(t, DrivePull, []string{ "+pull", "--local-dir", "local", "--folder-token", "folder_root", "--on-duplicate-remote", "rename", "--as", "bot", }, f, stdout) if err != nil { t.Fatalf("unexpected error: %v\nstdout: %s", err, stdout.String()) } occupied := occupiedRemotePaths([]driveRemoteEntry{ {RelPath: "dup.txt"}, {RelPath: "dup.txt"}, {RelPath: shortHashRelPath}, }) strongerRelPath, err := relPathWithUniqueFileTokenSuffix("dup.txt", duplicateRemoteFileIDSecond, occupied) if err != nil { t.Fatalf("relPathWithUniqueFileTokenSuffix: %v", err) } if strongerRelPath == shortHashRelPath { t.Fatalf("expected stronger unique suffix when %q is already occupied", shortHashRelPath) } mustReadFile(t, filepath.Join("local", shortHashRelPath), "THIRD") mustReadFile(t, filepath.Join("local", strongerRelPath), "SECOND") payload := decodeDrivePullStdout(t, stdout.Bytes()) if got := payload.Data.Summary.Downloaded; got != 3 { t.Fatalf("summary.downloaded = %d, want 3", got) } if item := findPullItem(payload.Data.Items, strongerRelPath); item.SourceID == "" || item.FileToken != "" { t.Fatalf("rename item should emit source_id without file_token, got: %#v", item) } assertPullItemAction(t, stdout.Bytes(), strongerRelPath, "downloaded") reg.Verify(t) } func TestDrivePullRenameAppendsSequenceWhenAllHashSuffixTargetsAreOccupied(t *testing.T) { fileToken := duplicateRemoteFileIDSecond tokenHash := stableTokenHash(fileToken) occupied := map[string]struct{}{ "dup.txt": {}, relPathWithSuffix("dup.txt", "__lark_"+tokenHash[:12]): {}, relPathWithSuffix("dup.txt", "__lark_"+tokenHash[:24]): {}, relPathWithSuffix("dup.txt", "__lark_"+tokenHash): {}, relPathWithSuffix("dup.txt", "__lark_"+tokenHash+"_2"): {}, } got, err := relPathWithUniqueFileTokenSuffix("dup.txt", fileToken, occupied) if err != nil { t.Fatalf("relPathWithUniqueFileTokenSuffix: %v", err) } want := relPathWithSuffix("dup.txt", "__lark_"+tokenHash+"_3") if got != want { t.Fatalf("unique rel_path = %q, want %q", got, want) } } func TestRelPathWithUniqueFileTokenSuffixReturnsErrorAfterMaxAttempts(t *testing.T) { fileToken := duplicateRemoteFileIDSecond tokenHash := stableTokenHash(fileToken) occupied := map[string]struct{}{ "dup.txt": {}, } for _, suffix := range []string{ "__lark_" + tokenHash[:12], "__lark_" + tokenHash[:24], "__lark_" + tokenHash, } { occupied[relPathWithSuffix("dup.txt", suffix)] = struct{}{} } for attempt := 2; attempt <= driveUniqueSuffixMaxSeq; attempt++ { occupied[relPathWithSuffix("dup.txt", "__lark_"+tokenHash+"_"+strconv.Itoa(attempt))] = struct{}{} } _, err := relPathWithUniqueFileTokenSuffix("dup.txt", fileToken, occupied) if err == nil { t.Fatal("expected relPathWithUniqueFileTokenSuffix to fail after exhausting all suffix attempts") } } func TestDrivePullNewestChoosesMostRecentDuplicateRemoteFile(t *testing.T) { f, stdout, _, reg := cmdutil.TestFactory(t, driveTestConfig()) tmpDir := t.TempDir() withDriveWorkingDir(t, tmpDir) if err := os.MkdirAll("local", 0o755); err != nil { t.Fatalf("MkdirAll: %v", err) } registerDuplicateRemoteFiles(reg) registerDownload(reg, duplicateRemoteFileIDSecond, "SECOND") err := mountAndRunDrive(t, DrivePull, []string{ "+pull", "--local-dir", "local", "--folder-token", "folder_root", "--on-duplicate-remote", "newest", "--as", "bot", }, f, stdout) if err != nil { t.Fatalf("unexpected error: %v\nstdout: %s", err, stdout.String()) } mustReadFile(t, filepath.Join("local", "dup.txt"), "SECOND") assertPullItemAction(t, stdout.Bytes(), "dup.txt", "downloaded") payload := decodeDrivePullStdout(t, stdout.Bytes()) if got := payload.Data.Summary.Downloaded; got != 1 { t.Fatalf("summary.downloaded = %d, want 1", got) } if item := findPullItem(payload.Data.Items, "dup.txt"); item.FileToken != duplicateRemoteFileIDSecond { t.Fatalf("stdout should surface the chosen newest file token, got: %#v", item) } reg.Verify(t) } func TestDrivePullOldestChoosesOldestDuplicateRemoteFile(t *testing.T) { f, stdout, _, reg := cmdutil.TestFactory(t, driveTestConfig()) tmpDir := t.TempDir() withDriveWorkingDir(t, tmpDir) if err := os.MkdirAll("local", 0o755); err != nil { t.Fatalf("MkdirAll: %v", err) } registerDuplicateRemoteFiles(reg) registerDownload(reg, duplicateRemoteFileIDFirst, "FIRST") err := mountAndRunDrive(t, DrivePull, []string{ "+pull", "--local-dir", "local", "--folder-token", "folder_root", "--on-duplicate-remote", "oldest", "--as", "bot", }, f, stdout) if err != nil { t.Fatalf("unexpected error: %v\nstdout: %s", err, stdout.String()) } mustReadFile(t, filepath.Join("local", "dup.txt"), "FIRST") assertPullItemAction(t, stdout.Bytes(), "dup.txt", "downloaded") payload := decodeDrivePullStdout(t, stdout.Bytes()) if got := payload.Data.Summary.Downloaded; got != 1 { t.Fatalf("summary.downloaded = %d, want 1", got) } if item := findPullItem(payload.Data.Items, "dup.txt"); item.FileToken != duplicateRemoteFileIDFirst { t.Fatalf("stdout should surface the chosen oldest file token, got: %#v", item) } reg.Verify(t) } func TestDrivePullRenameHandlesNestedDuplicateRemoteFilesEndToEnd(t *testing.T) { f, stdout, _, reg := cmdutil.TestFactory(t, driveTestConfig()) tmpDir := t.TempDir() withDriveWorkingDir(t, tmpDir) if err := os.MkdirAll("local", 0o755); err != nil { t.Fatalf("MkdirAll: %v", err) } registerRemoteListing(reg, "folder_root", []map[string]interface{}{ {"token": duplicateRemoteFolderID, "name": "sub", "type": "folder", "created_time": "1", "modified_time": "1"}, }) registerRemoteListing(reg, duplicateRemoteFolderID, []map[string]interface{}{ {"token": duplicateRemoteFileIDFirst, "name": "dup.txt", "type": "file", "size": 5, "created_time": "1", "modified_time": "1"}, {"token": duplicateRemoteFileIDSecond, "name": "dup.txt", "type": "file", "size": 6, "created_time": "2", "modified_time": "2"}, }) registerDownload(reg, duplicateRemoteFileIDFirst, "FIRST") registerDownload(reg, duplicateRemoteFileIDSecond, "SECOND") err := mountAndRunDrive(t, DrivePull, []string{ "+pull", "--local-dir", "local", "--folder-token", "folder_root", "--on-duplicate-remote", "rename", "--as", "bot", }, f, stdout) if err != nil { t.Fatalf("unexpected error: %v\nstdout: %s", err, stdout.String()) } renamedRelPath := expectedRenamedRelPath("sub/dup.txt", duplicateRemoteFileIDSecond, 12, 0) mustReadFile(t, filepath.Join("local", "sub", "dup.txt"), "FIRST") mustReadFile(t, filepath.Join("local", filepath.FromSlash(renamedRelPath)), "SECOND") assertPullItemAction(t, stdout.Bytes(), "sub/dup.txt", "downloaded") assertPullItemAction(t, stdout.Bytes(), renamedRelPath, "downloaded") reg.Verify(t) } func TestDrivePushFailsOnDuplicateRemoteFilesBeforeUpload(t *testing.T) { f, stdout, _, reg := cmdutil.TestFactory(t, driveTestConfig()) tmpDir := t.TempDir() withDriveWorkingDir(t, tmpDir) if err := os.MkdirAll("local", 0o755); err != nil { t.Fatalf("MkdirAll: %v", err) } if err := os.WriteFile(filepath.Join("local", "dup.txt"), []byte("LOCAL"), 0o644); err != nil { t.Fatalf("WriteFile: %v", err) } registerDuplicateRemoteFiles(reg) err := mountAndRunDrive(t, DrivePush, []string{ "+push", "--local-dir", "local", "--folder-token", "folder_root", "--if-exists", "overwrite", "--delete-remote", "--yes", "--as", "bot", }, f, stdout) assertDuplicateRemotePathError(t, err, "dup.txt", duplicateRemoteFileIDFirst, duplicateRemoteFileIDSecond) if stdout.String() != "" { t.Fatalf("stdout should be empty on duplicate_remote_path, got: %s", stdout.String()) } reg.Verify(t) } func TestDrivePullFailsOnRemoteFileFolderConflictEvenWithRename(t *testing.T) { f, stdout, _, reg := cmdutil.TestFactory(t, driveTestConfig()) tmpDir := t.TempDir() withDriveWorkingDir(t, tmpDir) if err := os.MkdirAll("local", 0o755); err != nil { t.Fatalf("MkdirAll: %v", err) } registerRemoteListing(reg, "folder_root", []map[string]interface{}{ {"token": duplicateRemoteFileIDFirst, "name": "dup", "type": "file", "size": 5, "created_time": "1", "modified_time": "1"}, {"token": duplicateRemoteFolderID, "name": "dup", "type": "folder", "created_time": "2", "modified_time": "2"}, }) registerRemoteListing(reg, duplicateRemoteFolderID, nil) err := mountAndRunDrive(t, DrivePull, []string{ "+pull", "--local-dir", "local", "--folder-token", "folder_root", "--on-duplicate-remote", "rename", "--as", "bot", }, f, stdout) assertDuplicateRemotePathError(t, err, "dup", duplicateRemoteFileIDFirst, duplicateRemoteFolderID) if stdout.String() != "" { t.Fatalf("stdout should be empty on duplicate_remote_path, got: %s", stdout.String()) } reg.Verify(t) } func TestDrivePushFailsOnRemoteFileFolderConflictEvenWithNewest(t *testing.T) { f, stdout, _, reg := cmdutil.TestFactory(t, driveTestConfig()) tmpDir := t.TempDir() withDriveWorkingDir(t, tmpDir) if err := os.MkdirAll("local", 0o755); err != nil { t.Fatalf("MkdirAll: %v", err) } if err := os.WriteFile(filepath.Join("local", "dup"), []byte("LOCAL"), 0o644); err != nil { t.Fatalf("WriteFile: %v", err) } registerRemoteListing(reg, "folder_root", []map[string]interface{}{ {"token": duplicateRemoteFileIDFirst, "name": "dup", "type": "file", "size": 5, "created_time": "1", "modified_time": "1"}, {"token": duplicateRemoteFolderID, "name": "dup", "type": "folder", "created_time": "2", "modified_time": "2"}, }) registerRemoteListing(reg, duplicateRemoteFolderID, nil) err := mountAndRunDrive(t, DrivePush, []string{ "+push", "--local-dir", "local", "--folder-token", "folder_root", "--on-duplicate-remote", "newest", "--if-exists", "skip", "--as", "bot", }, f, stdout) assertDuplicateRemotePathError(t, err, "dup", duplicateRemoteFileIDFirst, duplicateRemoteFolderID) if stdout.String() != "" { t.Fatalf("stdout should be empty on duplicate_remote_path, got: %s", stdout.String()) } reg.Verify(t) } func TestDrivePushDeleteRemoteDeletesUnchosenDuplicateSibling(t *testing.T) { f, stdout, _, reg := cmdutil.TestFactory(t, driveTestConfig()) tmpDir := t.TempDir() withDriveWorkingDir(t, tmpDir) if err := os.MkdirAll("local", 0o755); err != nil { t.Fatalf("MkdirAll: %v", err) } if err := os.WriteFile(filepath.Join("local", "dup.txt"), []byte("LOCAL"), 0o644); err != nil { t.Fatalf("WriteFile: %v", err) } registerDuplicateRemoteFiles(reg) reg.Register(&httpmock.Stub{ Method: "DELETE", URL: "/open-apis/drive/v1/files/" + duplicateRemoteFileIDFirst, Body: map[string]interface{}{ "code": 0, "msg": "ok", "data": map[string]interface{}{}, }, }) err := mountAndRunDrive(t, DrivePush, []string{ "+push", "--local-dir", "local", "--folder-token", "folder_root", "--if-exists", "skip", "--on-duplicate-remote", "newest", "--delete-remote", "--yes", "--as", "bot", }, f, stdout) if err != nil { t.Fatalf("unexpected error: %v\nstdout: %s", err, stdout.String()) } assertPushItemAction(t, stdout.Bytes(), "dup.txt", "deleted_remote", duplicateRemoteFileIDFirst) reg.Verify(t) } func TestDrivePushOldestOverwritesChosenDuplicateAndDeletesSibling(t *testing.T) { f, stdout, _, reg := cmdutil.TestFactory(t, driveTestConfig()) tmpDir := t.TempDir() withDriveWorkingDir(t, tmpDir) if err := os.MkdirAll("local", 0o755); err != nil { t.Fatalf("MkdirAll: %v", err) } if err := os.WriteFile(filepath.Join("local", "dup.txt"), []byte("LOCAL"), 0o644); err != nil { t.Fatalf("WriteFile: %v", err) } registerDuplicateRemoteFiles(reg) uploadStub := &httpmock.Stub{ Method: "POST", URL: "/open-apis/drive/v1/files/upload_all", Body: map[string]interface{}{ "code": 0, "msg": "ok", "data": map[string]interface{}{ "file_token": "dup-oldest-new-token", "version": "v11", }, }, } reg.Register(uploadStub) deleteStub := &httpmock.Stub{ Method: "DELETE", URL: "/open-apis/drive/v1/files/" + duplicateRemoteFileIDSecond, Body: map[string]interface{}{"code": 0, "msg": "ok"}, } reg.Register(deleteStub) err := mountAndRunDrive(t, DrivePush, []string{ "+push", "--local-dir", "local", "--folder-token", "folder_root", "--if-exists", "overwrite", "--on-duplicate-remote", "oldest", "--delete-remote", "--yes", "--as", "bot", }, f, stdout) if err != nil { t.Fatalf("unexpected error: %v\nstdout: %s", err, stdout.String()) } body := decodeDriveMultipartBody(t, uploadStub) if got := body.Fields["file_token"]; got != duplicateRemoteFileIDFirst { t.Fatalf("upload_all form file_token = %q, want %q", got, duplicateRemoteFileIDFirst) } assertPushItemAction(t, stdout.Bytes(), "dup.txt", "deleted_remote", duplicateRemoteFileIDSecond) if deleteStub.CapturedHeaders == nil { t.Fatal("DELETE for the newer duplicate sibling was never issued") } reg.Verify(t) } func TestDrivePushNewestResolvesNestedDuplicateRemoteFilesEndToEnd(t *testing.T) { f, stdout, _, reg := cmdutil.TestFactory(t, driveTestConfig()) tmpDir := t.TempDir() withDriveWorkingDir(t, tmpDir) if err := os.MkdirAll(filepath.Join("local", "sub"), 0o755); err != nil { t.Fatalf("MkdirAll: %v", err) } if err := os.WriteFile(filepath.Join("local", "sub", "dup.txt"), []byte("LOCAL"), 0o644); err != nil { t.Fatalf("WriteFile: %v", err) } registerRemoteListing(reg, "folder_root", []map[string]interface{}{ {"token": duplicateRemoteFolderID, "name": "sub", "type": "folder", "created_time": "1", "modified_time": "1"}, }) registerRemoteListing(reg, duplicateRemoteFolderID, []map[string]interface{}{ {"token": duplicateRemoteFileIDFirst, "name": "dup.txt", "type": "file", "size": 5, "created_time": "1", "modified_time": "1"}, {"token": duplicateRemoteFileIDSecond, "name": "dup.txt", "type": "file", "size": 6, "created_time": "2", "modified_time": "2"}, }) uploadStub := &httpmock.Stub{ Method: "POST", URL: "/open-apis/drive/v1/files/upload_all", Body: map[string]interface{}{ "code": 0, "msg": "ok", "data": map[string]interface{}{ "file_token": "nested-dup-new-token", "version": "v7", }, }, } reg.Register(uploadStub) deleteStub := &httpmock.Stub{ Method: "DELETE", URL: "/open-apis/drive/v1/files/" + duplicateRemoteFileIDFirst, Body: map[string]interface{}{"code": 0, "msg": "ok"}, } reg.Register(deleteStub) err := mountAndRunDrive(t, DrivePush, []string{ "+push", "--local-dir", "local", "--folder-token", "folder_root", "--if-exists", "overwrite", "--on-duplicate-remote", "newest", "--delete-remote", "--yes", "--as", "bot", }, f, stdout) if err != nil { t.Fatalf("unexpected error: %v\nstdout: %s", err, stdout.String()) } body := decodeDriveMultipartBody(t, uploadStub) if got := body.Fields["file_token"]; got != duplicateRemoteFileIDSecond { t.Fatalf("upload_all form file_token = %q, want %q", got, duplicateRemoteFileIDSecond) } assertPushItemAction(t, stdout.Bytes(), "sub/dup.txt", "deleted_remote", duplicateRemoteFileIDFirst) if deleteStub.CapturedHeaders == nil { t.Fatal("DELETE for nested duplicate sibling was never issued") } reg.Verify(t) } func TestChooseRemoteFileSortsByParsedTimes(t *testing.T) { files := []driveRemoteEntry{ {FileToken: "token_b", CreatedTime: "9", ModifiedTime: "9"}, {FileToken: "token_a", CreatedTime: "10", ModifiedTime: "10"}, } gotNewest, err := chooseRemoteFile(files, driveDuplicateRemoteNewest) if err != nil { t.Fatalf("chooseRemoteFile newest: %v", err) } if gotNewest.FileToken != "token_a" { t.Fatalf("newest token = %q, want token_a", gotNewest.FileToken) } gotOldest, err := chooseRemoteFile(files, driveDuplicateRemoteOldest) if err != nil { t.Fatalf("chooseRemoteFile oldest: %v", err) } if gotOldest.FileToken != "token_b" { t.Fatalf("oldest token = %q, want token_b", gotOldest.FileToken) } } // TestChooseRemoteFileSortsMixedUnitEpochsByActualTime verifies duplicate // resolution compares actual timestamps rather than raw integer magnitudes when // Drive mixes second- and millisecond-resolution epoch strings. func TestChooseRemoteFileSortsMixedUnitEpochsByActualTime(t *testing.T) { files := []driveRemoteEntry{ {FileToken: "token_seconds", CreatedTime: "1715594881", ModifiedTime: "1715594881"}, {FileToken: "token_millis", CreatedTime: "1715594880123", ModifiedTime: "1715594880123"}, } gotNewest, err := chooseRemoteFile(files, driveDuplicateRemoteNewest) if err != nil { t.Fatalf("chooseRemoteFile newest: %v", err) } if gotNewest.FileToken != "token_seconds" { t.Fatalf("newest token = %q, want token_seconds", gotNewest.FileToken) } gotOldest, err := chooseRemoteFile(files, driveDuplicateRemoteOldest) if err != nil { t.Fatalf("chooseRemoteFile oldest: %v", err) } if gotOldest.FileToken != "token_millis" { t.Fatalf("oldest token = %q, want token_millis", gotOldest.FileToken) } } // TestDrivePushDeleteRemoteKeepsActualNewestDuplicateAcrossMixedEpochUnits // proves the duplicate selector and delete pass agree on the true newest file // even when remote timestamps use mixed epoch units. func TestDrivePushDeleteRemoteKeepsActualNewestDuplicateAcrossMixedEpochUnits(t *testing.T) { f, stdout, _, reg := cmdutil.TestFactory(t, driveTestConfig()) tmpDir := t.TempDir() withDriveWorkingDir(t, tmpDir) if err := os.MkdirAll("local", 0o755); err != nil { t.Fatalf("MkdirAll: %v", err) } if err := os.WriteFile(filepath.Join("local", "dup.txt"), []byte("hello"), 0o644); err != nil { t.Fatalf("WriteFile: %v", err) } registerRemoteListing(reg, "folder_root", []map[string]interface{}{ {"token": duplicateRemoteFileIDFirst, "name": "dup.txt", "type": "file", "size": 5, "created_time": "1715594880123", "modified_time": "1715594880123"}, {"token": duplicateRemoteFileIDSecond, "name": "dup.txt", "type": "file", "size": 6, "created_time": "1715594881", "modified_time": "1715594881"}, }) uploadStub := &httpmock.Stub{ Method: "POST", URL: "/open-apis/drive/v1/files/upload_all", Body: map[string]interface{}{ "code": 0, "msg": "ok", "data": map[string]interface{}{ "file_token": "dup-new-token", "version": "v7", }, }, } reg.Register(uploadStub) deleteStub := &httpmock.Stub{ Method: "DELETE", URL: "/open-apis/drive/v1/files/" + duplicateRemoteFileIDFirst, Body: map[string]interface{}{"code": 0, "msg": "ok"}, } reg.Register(deleteStub) err := mountAndRunDrive(t, DrivePush, []string{ "+push", "--local-dir", "local", "--folder-token", "folder_root", "--if-exists", "overwrite", "--on-duplicate-remote", "newest", "--delete-remote", "--yes", "--as", "bot", }, f, stdout) if err != nil { t.Fatalf("unexpected error: %v\nstdout: %s", err, stdout.String()) } body := decodeDriveMultipartBody(t, uploadStub) if got := body.Fields["file_token"]; got != duplicateRemoteFileIDSecond { t.Fatalf("upload_all form file_token = %q, want %q", got, duplicateRemoteFileIDSecond) } assertPushItemAction(t, stdout.Bytes(), "dup.txt", "deleted_remote", duplicateRemoteFileIDFirst) if deleteStub.CapturedHeaders == nil { t.Fatal("DELETE for older mixed-unit duplicate sibling was never issued") } reg.Verify(t) } func TestChooseRemoteFileFallsBackToFileTokenOnTimeParseFailure(t *testing.T) { files := []driveRemoteEntry{ {FileToken: "token_a", CreatedTime: "bad", ModifiedTime: "bad"}, {FileToken: "token_b", CreatedTime: "10", ModifiedTime: "10"}, } got, err := chooseRemoteFile(files, driveDuplicateRemoteNewest) if err != nil { t.Fatalf("chooseRemoteFile: %v", err) } if got.FileToken != "token_a" { t.Fatalf("fallback token = %q, want token_a", got.FileToken) } } func TestChooseRemoteFileRejectsEmptyCandidates(t *testing.T) { _, err := chooseRemoteFile(nil, driveDuplicateRemoteNewest) if err == nil { t.Fatal("expected chooseRemoteFile to reject empty candidates") } } func TestCompareDriveRemoteModifiedToLocalSupportsSecondAndMillisecondEpochs(t *testing.T) { t.Run("second resolution truncates local mtime", func(t *testing.T) { cmp, ok := compareDriveRemoteModifiedToLocal("100", time.Unix(100, 900*int64(time.Millisecond))) if !ok { t.Fatal("expected second-resolution timestamp to parse") } if cmp != 0 { t.Fatalf("cmp = %d, want 0 when local only differs below second resolution", cmp) } }) t.Run("millisecond resolution stays precise", func(t *testing.T) { const remoteMillis = int64(1715594880123) cmp, ok := compareDriveRemoteModifiedToLocal(strconv.FormatInt(remoteMillis, 10), time.UnixMilli(remoteMillis)) if !ok { t.Fatal("expected millisecond-resolution timestamp to parse") } if cmp != 0 { t.Fatalf("cmp = %d, want 0 for equal millisecond timestamps", cmp) } }) t.Run("microsecond resolution stays precise", func(t *testing.T) { const remoteMicros = int64(1715594880123456) cmp, ok := compareDriveRemoteModifiedToLocal(strconv.FormatInt(remoteMicros, 10), time.UnixMicro(remoteMicros)) if !ok { t.Fatal("expected microsecond-resolution timestamp to parse") } if cmp != 0 { t.Fatalf("cmp = %d, want 0 for equal microsecond timestamps", cmp) } }) t.Run("invalid timestamp is rejected", func(t *testing.T) { if _, ok := compareDriveRemoteModifiedToLocal("not-a-time", time.Now()); ok { t.Fatal("expected invalid remote timestamp to be rejected") } }) } func TestDrivePullRemoteViewsRejectsUnknownStrategy(t *testing.T) { _, _, err := drivePullRemoteViews([]driveRemoteEntry{ {RelPath: "dup.txt", Type: driveTypeFile, FileToken: duplicateRemoteFileIDFirst}, {RelPath: "dup.txt", Type: driveTypeFile, FileToken: duplicateRemoteFileIDSecond}, }, "mystery") if err == nil { t.Fatal("expected drivePullRemoteViews to reject an unknown duplicate strategy") } } func registerDuplicateRemoteFiles(reg *httpmock.Registry) { registerRemoteListing(reg, "folder_root", []map[string]interface{}{ {"token": duplicateRemoteFileIDFirst, "name": "dup.txt", "type": "file", "size": 5, "created_time": "1", "modified_time": "1"}, {"token": duplicateRemoteFileIDSecond, "name": "dup.txt", "type": "file", "size": 6, "created_time": "2", "modified_time": "2"}, }) } func registerRemoteListing(reg *httpmock.Registry, folderToken string, files []map[string]interface{}) { items := make([]interface{}, 0, len(files)) for _, file := range files { items = append(items, file) } reg.Register(&httpmock.Stub{ Method: "GET", URL: "folder_token=" + folderToken, Body: map[string]interface{}{ "code": 0, "msg": "ok", "data": map[string]interface{}{ "files": items, "has_more": false, }, }, }) } func registerDownload(reg *httpmock.Registry, fileToken, body string) { reg.Register(&httpmock.Stub{ Method: "GET", URL: "/open-apis/drive/v1/files/" + fileToken + "/download", Status: 200, Body: []byte(body), Headers: http.Header{"Content-Type": []string{"application/octet-stream"}}, }) } func assertDuplicateRemotePathError(t *testing.T, err error, relPath string, tokens ...string) { t.Helper() if err == nil { t.Fatal("expected duplicate rel_path validation error, got nil") } var validationErr *errs.ValidationError if !errors.As(err, &validationErr) { t.Fatalf("expected *errs.ValidationError, got %T: %v", err, err) } if validationErr.Subtype != errs.SubtypeFailedPrecondition { t.Fatalf("subtype = %q, want %q", validationErr.Subtype, errs.SubtypeFailedPrecondition) } if validationErr.Hint == "" { t.Fatal("duplicate validation error should carry a recovery hint so AI consumers know the next action") } if len(validationErr.Params) == 0 { t.Fatal("duplicate validation error should carry at least one param") } var matched *errs.InvalidParam for i := range validationErr.Params { if validationErr.Params[i].Name == relPath { matched = &validationErr.Params[i] break } } if matched == nil { t.Fatalf("duplicate params missing rel_path group %q: %#v", relPath, validationErr.Params) } if matched.Reason == "" { t.Fatalf("duplicate param for rel_path %q missing reason", relPath) } for _, token := range tokens { if !strings.Contains(matched.Reason, token) { t.Fatalf("duplicate param reason missing token %q: %s", token, matched.Reason) } } } type drivePullStdoutPayload struct { Data struct { Summary struct { Downloaded int `json:"downloaded"` Skipped int `json:"skipped"` Failed int `json:"failed"` } `json:"summary"` Items []struct { RelPath string `json:"rel_path"` FileToken string `json:"file_token,omitempty"` SourceID string `json:"source_id,omitempty"` Action string `json:"action"` } `json:"items"` } `json:"data"` } func decodeDrivePullStdout(t *testing.T, raw []byte) drivePullStdoutPayload { t.Helper() var payload drivePullStdoutPayload if err := json.Unmarshal(raw, &payload); err != nil { t.Fatalf("decode pull stdout: %v\n%s", err, string(raw)) } return payload } func findPullItem(items []struct { RelPath string `json:"rel_path"` FileToken string `json:"file_token,omitempty"` SourceID string `json:"source_id,omitempty"` Action string `json:"action"` }, relPath string) struct { RelPath string `json:"rel_path"` FileToken string `json:"file_token,omitempty"` SourceID string `json:"source_id,omitempty"` Action string `json:"action"` } { for _, item := range items { if item.RelPath == relPath { return item } } return struct { RelPath string `json:"rel_path"` FileToken string `json:"file_token,omitempty"` SourceID string `json:"source_id,omitempty"` Action string `json:"action"` }{} } func expectedRenamedRelPath(relPath, fileToken string, hashLen, attempt int) string { sum := sha256.Sum256([]byte(fileToken)) hash := hex.EncodeToString(sum[:]) suffix := "__lark_" + hash[:hashLen] if attempt > 0 { suffix = "__lark_" + hash + "_" + strconv.Itoa(attempt) } dir, base := path.Split(relPath) ext := path.Ext(base) if ext == base { return dir + base + suffix } stem := base[:len(base)-len(ext)] return dir + stem + suffix + ext } func assertPullItemAction(t *testing.T, raw []byte, relPath, action string) { t.Helper() var payload struct { Data struct { Items []struct { RelPath string `json:"rel_path"` Action string `json:"action"` } `json:"items"` } `json:"data"` } if err := json.Unmarshal(raw, &payload); err != nil { t.Fatalf("decode pull stdout: %v\n%s", err, string(raw)) } for _, item := range payload.Data.Items { if item.RelPath == relPath && item.Action == action { return } } t.Fatalf("missing pull item %q/%q in stdout: %s", relPath, action, string(raw)) } func assertPushItemAction(t *testing.T, raw []byte, relPath, action, fileToken string) { t.Helper() var payload struct { Data struct { Items []struct { RelPath string `json:"rel_path"` Action string `json:"action"` FileToken string `json:"file_token"` } `json:"items"` } `json:"data"` } if err := json.Unmarshal(raw, &payload); err != nil { t.Fatalf("decode push stdout: %v\n%s", err, string(raw)) } for _, item := range payload.Data.Items { if item.RelPath == relPath && item.Action == action && item.FileToken == fileToken { return } } t.Fatalf("missing push item %q/%q/%q in stdout: %s", relPath, action, fileToken, string(raw)) }