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 stable) (pull_request) Successful in 1m9s
Tests / Test (Go 1.25) (pull_request) Successful in 1m11s
261 lines
7.3 KiB
Go
261 lines
7.3 KiB
Go
package main
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
"io"
|
|
"strings"
|
|
"testing"
|
|
|
|
onlyoffice "github.com/eslider/go-onlyoffice"
|
|
)
|
|
|
|
// fakeFolderStore is an in-memory onlyoffice.FileStore for the ensure-path
|
|
// logic. It records created folders so idempotency can be asserted.
|
|
type fakeFolderStore struct {
|
|
nextID int
|
|
entries map[string][]onlyoffice.Entry
|
|
created []string
|
|
}
|
|
|
|
func newFakeFolderStore() *fakeFolderStore {
|
|
return &fakeFolderStore{entries: map[string][]onlyoffice.Entry{}}
|
|
}
|
|
|
|
func (f *fakeFolderStore) Name() string { return "fake" }
|
|
|
|
func (f *fakeFolderStore) List(_ context.Context, parent string) ([]onlyoffice.Entry, error) {
|
|
return append([]onlyoffice.Entry(nil), f.entries[parent]...), nil
|
|
}
|
|
|
|
func (f *fakeFolderStore) CreateFolder(_ context.Context, parent, title string) (onlyoffice.Entry, error) {
|
|
f.nextID++
|
|
e := onlyoffice.Entry{
|
|
ID: fmt.Sprintf("id-%d", f.nextID),
|
|
ParentID: parent,
|
|
Title: title,
|
|
Kind: onlyoffice.Folder,
|
|
}
|
|
f.entries[parent] = append(f.entries[parent], e)
|
|
f.created = append(f.created, parent+"/"+title)
|
|
return e, nil
|
|
}
|
|
|
|
func (f *fakeFolderStore) Stat(context.Context, string) (onlyoffice.Entry, error) {
|
|
return onlyoffice.Entry{}, errors.New("not implemented")
|
|
}
|
|
|
|
func (f *fakeFolderStore) Upload(context.Context, string, string, io.Reader) (onlyoffice.Entry, error) {
|
|
return onlyoffice.Entry{}, errors.New("not implemented")
|
|
}
|
|
|
|
func (f *fakeFolderStore) Download(context.Context, string, io.Writer) (int64, error) {
|
|
return 0, errors.New("not implemented")
|
|
}
|
|
|
|
func (f *fakeFolderStore) Move(context.Context, []string, string) error {
|
|
return errors.New("not implemented")
|
|
}
|
|
|
|
func (f *fakeFolderStore) Copy(context.Context, []string, string) error {
|
|
return errors.New("not implemented")
|
|
}
|
|
|
|
func (f *fakeFolderStore) Rename(context.Context, string, string) error {
|
|
return errors.New("not implemented")
|
|
}
|
|
|
|
func (f *fakeFolderStore) Delete(context.Context, []string) error {
|
|
return errors.New("not implemented")
|
|
}
|
|
|
|
// fakeUploader is the *onlyoffice.Client slice dav upload depends on.
|
|
type fakeUploader struct {
|
|
conflict bool
|
|
uploads []string
|
|
replaced []string
|
|
}
|
|
|
|
func (f *fakeUploader) UploadToFolderReplacing(_ context.Context, folderID, localPath string) (*onlyoffice.FileEntry, []int, error) {
|
|
f.uploads = append(f.uploads, localPath)
|
|
ids := []int(nil)
|
|
if f.conflict {
|
|
ids = []int{7}
|
|
f.replaced = append(f.replaced, folderID+"#7")
|
|
}
|
|
return fakeFileEntry(99, localPath, folderID), ids, nil
|
|
}
|
|
|
|
func (f *fakeUploader) AssertNoFileConflict(_ context.Context, folderID, localPath string) error {
|
|
if f.conflict {
|
|
return fmt.Errorf("%w: conflict in folder %s for %s", onlyoffice.ErrFileExists, folderID, localPath)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (f *fakeUploader) UploadToFolder(_ context.Context, folderID, localPath string) (*onlyoffice.FileEntry, error) {
|
|
f.uploads = append(f.uploads, localPath)
|
|
return fakeFileEntry(99, localPath, folderID), nil
|
|
}
|
|
|
|
func fakeFileEntry(id int, localPath, folderID string) *onlyoffice.FileEntry {
|
|
num := json.Number(fmt.Sprintf("%d", id))
|
|
title := localPath[strings.LastIndex(localPath, "/")+1:]
|
|
return &onlyoffice.FileEntry{
|
|
ID: &num,
|
|
Title: &title,
|
|
FolderID: &num,
|
|
}
|
|
}
|
|
|
|
func TestEnsurePathCreatesNestedFolders(t *testing.T) {
|
|
store := newFakeFolderStore()
|
|
ctx := context.Background()
|
|
|
|
got, err := ensurePath(ctx, store, "root", "Banks/Caixa")
|
|
if err != nil {
|
|
t.Fatalf("ensurePath: %v", err)
|
|
}
|
|
if got.Kind != onlyoffice.Folder || got.ID == "" {
|
|
t.Fatalf("ensurePath returned %+v, want a folder with an id", got)
|
|
}
|
|
if want := []string{"root/Banks", "id-1/Caixa"}; !equalStrings(store.created, want) {
|
|
t.Fatalf("created %v, want %v", store.created, want)
|
|
}
|
|
}
|
|
|
|
func TestEnsurePathIsIdempotent(t *testing.T) {
|
|
store := newFakeFolderStore()
|
|
ctx := context.Background()
|
|
|
|
first, err := ensurePath(ctx, store, "root", "Banks/Caixa")
|
|
if err != nil {
|
|
t.Fatalf("ensurePath first: %v", err)
|
|
}
|
|
created := len(store.created)
|
|
|
|
second, err := ensurePath(ctx, store, "root", "Banks/Caixa")
|
|
if err != nil {
|
|
t.Fatalf("ensurePath second: %v", err)
|
|
}
|
|
if second.ID != first.ID {
|
|
t.Fatalf("second run id = %q, want %q (no duplicate)", second.ID, first.ID)
|
|
}
|
|
if len(store.created) != created {
|
|
t.Fatalf("second run created folders: %v", store.created)
|
|
}
|
|
}
|
|
|
|
func TestEnsurePathReusesExistingFolder(t *testing.T) {
|
|
store := newFakeFolderStore()
|
|
store.entries["root"] = []onlyoffice.Entry{
|
|
{ID: "banks-id", Title: "Banks", Kind: onlyoffice.Folder},
|
|
}
|
|
store.entries["banks-id"] = []onlyoffice.Entry{
|
|
{ID: "caixa-id", Title: "Caixa", Kind: onlyoffice.Folder},
|
|
}
|
|
|
|
got, err := ensurePath(context.Background(), store, "root", "Banks/Caixa")
|
|
if err != nil {
|
|
t.Fatalf("ensurePath: %v", err)
|
|
}
|
|
if got.ID != "caixa-id" {
|
|
t.Fatalf("id = %q, want caixa-id", got.ID)
|
|
}
|
|
if len(store.created) != 0 {
|
|
t.Fatalf("created %v, want none", store.created)
|
|
}
|
|
}
|
|
|
|
func TestEnsurePathRejectsEmptyAndDotDot(t *testing.T) {
|
|
store := newFakeFolderStore()
|
|
for _, path := range []string{"", "/", "Banks/../Caixa"} {
|
|
if _, err := ensurePath(context.Background(), store, "root", path); err == nil {
|
|
t.Fatalf("ensurePath(%q) = nil error, want failure", path)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestUploadLocalReplacesByDefault(t *testing.T) {
|
|
up := &fakeUploader{conflict: true}
|
|
results, err := uploadLocal(context.Background(), up, "folder-1", []string{"a/f.xlsx"}, true)
|
|
if err != nil {
|
|
t.Fatalf("uploadLocal: %v", err)
|
|
}
|
|
if len(results) != 1 || results[0].Entry.Title != "f.xlsx" {
|
|
t.Fatalf("results = %+v", results)
|
|
}
|
|
if len(results[0].Replaced) != 1 || results[0].Replaced[0] != 7 {
|
|
t.Fatalf("replaced = %v, want [7]", results[0].Replaced)
|
|
}
|
|
if len(up.replaced) != 1 {
|
|
t.Fatalf("UploadToFolderReplacing not used for replace: %v", up.replaced)
|
|
}
|
|
}
|
|
|
|
func TestUploadLocalNoReplaceFailsOnConflict(t *testing.T) {
|
|
up := &fakeUploader{conflict: true}
|
|
_, err := uploadLocal(context.Background(), up, "folder-1", []string{"a/f.xlsx"}, false)
|
|
if !errors.Is(err, onlyoffice.ErrFileExists) {
|
|
t.Fatalf("err = %v, want ErrFileExists", err)
|
|
}
|
|
if len(up.uploads) != 0 {
|
|
t.Fatalf("uploaded despite conflict: %v", up.uploads)
|
|
}
|
|
}
|
|
|
|
func TestUploadLocalNoReplaceUploadsWhenFree(t *testing.T) {
|
|
up := &fakeUploader{}
|
|
results, err := uploadLocal(context.Background(), up, "folder-1", []string{"a/f.xlsx", "a/g.pdf"}, false)
|
|
if err != nil {
|
|
t.Fatalf("uploadLocal: %v", err)
|
|
}
|
|
if len(results) != 2 {
|
|
t.Fatalf("results = %+v", results)
|
|
}
|
|
if len(up.uploads) != 2 {
|
|
t.Fatalf("uploads = %v", up.uploads)
|
|
}
|
|
}
|
|
|
|
func TestDavRegistersUploadAndEnsurePath(t *testing.T) {
|
|
for _, name := range []string{"ensure-path", "upload"} {
|
|
cmd, _, err := rootCmd.Find([]string{"dav", name})
|
|
if err != nil {
|
|
t.Fatalf("dav %s not registered: %v", name, err)
|
|
}
|
|
if cmd.Name() != name {
|
|
t.Fatalf("resolved %q, want %q", cmd.Name(), name)
|
|
}
|
|
}
|
|
upload, _, err := rootCmd.Find([]string{"dav", "upload"})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
flag := upload.Flags().Lookup("replace")
|
|
if flag == nil || flag.DefValue != "true" {
|
|
t.Fatalf("upload --replace flag = %+v, want default true", flag)
|
|
}
|
|
ensure, _, err := rootCmd.Find([]string{"dav", "ensure-path"})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if ensure.Flags().Lookup("under") == nil {
|
|
t.Fatal("ensure-path missing --under flag")
|
|
}
|
|
}
|
|
|
|
func equalStrings(a, b []string) bool {
|
|
if len(a) != len(b) {
|
|
return false
|
|
}
|
|
for i := range a {
|
|
if a[i] != b[i] {
|
|
return false
|
|
}
|
|
}
|
|
return true
|
|
}
|