Files
go-onlyoffice/files_replace_test.go
T
eSlider 74e72d0e06
Release / GoReleaser (push) Skipped
Tests / Secret scan (gitleaks) (push) Skipped
Tests / Test (Go 1.25) (push) Skipped
Tests / Test (Go stable) (push) Skipped
Tests / Secret scan (gitleaks) (pull_request) Successful in 5s
Tests / Test (Go 1.25) (pull_request) Successful in 1m3s
Tests / Test (Go stable) (pull_request) Successful in 1m11s
fix(dav): upsert upload matches server-converted ext (xls→xlsx) (#82)
OnlyOffice converts legacy binary Office uploads (.xls/.doc/.ppt) into
OOXML (.xlsx/.docx/.pptx) on the server. UploadToFolderReplacing matched
by the exact stem|ext via FindFilesByDedupKey, so a repeated
`oo dav upload FOLDER f.xls --replace` never found the stored f.xlsx and
appended a second file (live: ids 3799+3887, 3800+3888).

- EquivalentUploadExt / FindFilesByStemExt: match by stem with a
  legacy↔OOXML extension equivalence, so .xls finds the saved .xlsx.
- planUploadReplacement: pick the surviving file and the redundant
  duplicate ids for a replacing upload.
- UploadToFolderReplacing updates the existing file in place (UpdateFile,
  stable id, no delete window), removes extra duplicates, and falls back
  to conversion-aware delete + upload when the portal rejects the update.
- AssertNoFileConflict (--no-replace) uses the same conversion-aware
  matching so a .xls upload conflicts with an existing .xlsx.
- Offline tests cover the matcher, the plan and the repeated-.xls
  regression; pdf/xlsx behaviour unchanged.
2026-09-27 15:05:13 +01:00

121 lines
4.6 KiB
Go

package onlyoffice
import (
"testing"
"time"
)
func TestEquivalentUploadExt(t *testing.T) {
tests := []struct {
a, b string
want bool
}{
{".xls", ".xlsx", true},
{".XLS", ".xlsx", true},
{"xls", "xlsx", true},
{".doc", ".docx", true},
{".ppt", ".pptx", true},
{".pdf", ".pdf", true},
{"", "", true},
{".xls", ".docx", false},
{".xlsx", "", false},
{".csv", ".xlsx", false},
}
for _, tc := range tests {
if got := EquivalentUploadExt(tc.a, tc.b); got != tc.want {
t.Errorf("EquivalentUploadExt(%q,%q)=%v want %v", tc.a, tc.b, got, tc.want)
}
if got := EquivalentUploadExt(tc.b, tc.a); got != tc.want {
t.Errorf("EquivalentUploadExt(%q,%q)=%v want %v (symmetric)", tc.b, tc.a, got, tc.want)
}
}
}
func TestFindFilesByStemExtMatchesConvertedXLS(t *testing.T) {
xlsx := &FileEntry{ID: jsonNum("3799"), Title: strPtr("ES29-extracto.xlsx"), FileExst: strPtr(".xlsx")}
pdf := &FileEntry{ID: jsonNum("5"), Title: strPtr("ES29-extracto.pdf"), FileExst: strPtr(".pdf")}
other := &FileEntry{ID: jsonNum("3887"), Title: strPtr("ES87-extracto.xlsx"), FileExst: strPtr(".xlsx")}
files := []*FileEntry{xlsx, pdf, other}
got := FindFilesByStemExt(files, "ES29-extracto", ".xls")
if len(got) != 1 || got[0] != xlsx {
t.Fatalf("converted .xls match = %+v, want the saved .xlsx only", got)
}
}
func TestFindFilesByStemExtKeepsExactMatch(t *testing.T) {
xlsx := &FileEntry{ID: jsonNum("3799"), Title: strPtr("foo.xlsx"), FileExst: strPtr(".xlsx")}
pdf := &FileEntry{ID: jsonNum("5"), Title: strPtr("foo.pdf"), FileExst: strPtr(".pdf")}
files := []*FileEntry{xlsx, pdf}
if got := FindFilesByStemExt(files, "foo", ".xlsx"); len(got) != 1 || got[0] != xlsx {
t.Fatalf("exact .xlsx match = %+v, want only xlsx", got)
}
if got := FindFilesByStemExt(files, "foo", ".pdf"); len(got) != 1 || got[0] != pdf {
t.Fatalf("exact .pdf match = %+v, want only pdf", got)
}
if got := FindFilesByStemExt(files, "foo", ".docx"); len(got) != 0 {
t.Fatalf("unrelated ext matched %+v, want none", got)
}
}
func TestFindFilesByStemExtEmptyStem(t *testing.T) {
f := &FileEntry{ID: jsonNum("1"), Title: strPtr("foo.xlsx"), FileExst: strPtr(".xlsx")}
if got := FindFilesByStemExt([]*FileEntry{f}, "", ".xlsx"); len(got) != 0 {
t.Fatalf("empty stem matched %+v, want none", got)
}
}
func TestPlanUploadReplacementFreshUpload(t *testing.T) {
plan := planUploadReplacement(nil, "ES29-extracto", ".xls")
if plan.UpdateID != "" || len(plan.DeleteIDs) != 0 {
t.Fatalf("empty folder plan = %+v, want a fresh upload", plan)
}
}
func TestPlanUploadReplacementUpdatesConvertedFile(t *testing.T) {
xlsx := &FileEntry{ID: jsonNum("3799"), Title: strPtr("ES29-extracto.xlsx"), FileExst: strPtr(".xlsx")}
plan := planUploadReplacement([]*FileEntry{xlsx}, "ES29-extracto", ".xls")
if plan.UpdateID != "3799" {
t.Fatalf("UpdateID = %q, want 3799 (update the saved .xlsx in place)", plan.UpdateID)
}
if len(plan.DeleteIDs) != 0 {
t.Fatalf("DeleteIDs = %v, want none", plan.DeleteIDs)
}
}
func TestPlanUploadReplacementCollapsesDuplicates(t *testing.T) {
older := time.Date(2026, 9, 1, 10, 0, 0, 0, time.UTC)
newer := older.Add(time.Hour)
first := &FileEntry{ID: jsonNum("3799"), Title: strPtr("ES29-extracto.xlsx"), FileExst: strPtr(".xlsx"), Updated: &older}
second := &FileEntry{ID: jsonNum("3887"), Title: strPtr("ES29-extracto.xlsx"), FileExst: strPtr(".xlsx"), Updated: &newer}
plan := planUploadReplacement([]*FileEntry{first, second}, "ES29-extracto", ".xls")
if plan.UpdateID != "3887" {
t.Fatalf("UpdateID = %q, want the newest duplicate 3887", plan.UpdateID)
}
if len(plan.DeleteIDs) != 1 || plan.DeleteIDs[0] != 3799 {
t.Fatalf("DeleteIDs = %v, want [3799]", plan.DeleteIDs)
}
}
// TestPlanUploadReplacementRepeatedXLS is the regression for #82: the second
// upload of the same .xls must find the server-converted .xlsx and update it,
// not append a second file.
func TestPlanUploadReplacementRepeatedXLS(t *testing.T) {
const stem = "ES29-extracto"
// First upload: nothing in the folder.
if plan := planUploadReplacement(nil, stem, ".xls"); plan.UpdateID != "" {
t.Fatalf("first upload plan = %+v, want create", plan)
}
// OnlyOffice converts .xls -> .xlsx on upload; second upload must match it.
saved := &FileEntry{ID: jsonNum("3799"), Title: strPtr(stem + ".xlsx"), FileExst: strPtr(".xlsx")}
plan := planUploadReplacement([]*FileEntry{saved}, stem, ".xls")
if plan.UpdateID != "3799" {
t.Fatalf("second upload plan = %+v, want in-place update of 3799 (no duplicate)", plan)
}
if len(plan.DeleteIDs) != 0 {
t.Fatalf("second upload would delete %v, want none", plan.DeleteIDs)
}
}