fix(dav): upsert upload matches server-converted ext (xls→xlsx) (#82)
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
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
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.
This commit is contained in:
@@ -0,0 +1,120 @@
|
||||
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)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user