Release Please / Release Please (push) Skipped
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 4s
Tests / Test (Go 1.25) (pull_request) Successful in 1m56s
Tests / Test (Go stable) (pull_request) Successful in 1m57s
140 lines
4.6 KiB
Go
140 lines
4.6 KiB
Go
//go:build integration
|
|
|
|
package onlyoffice
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"errors"
|
|
"os"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
// TestIntegrationPGStore exercises the read-only SQL backend against the live
|
|
// Community Server database and cross-checks list/stat/download with the REST
|
|
// FileStore. It needs ONLYOFFICE_DSN plus the usual ONLYOFFICE_URL/USER/PASS;
|
|
// ONLYOFFICE_PG_TEST_FILE_ID / ONLYOFFICE_PG_TEST_FOLDER_ID pick a real file
|
|
// (a file reachable over REST too). Download streams from MinIO, so it also
|
|
// needs MINIO_ACCESS_KEY/MINIO_SECRET_KEY.
|
|
//
|
|
// The live Community Server runs on MySQL; PostgreSQL is supported by the same
|
|
// code path when the DSN says so.
|
|
func TestIntegrationPGStore(t *testing.T) {
|
|
cfg := PGConfigFromEnv()
|
|
if strings.TrimSpace(cfg.DSN) == "" {
|
|
t.Skip("ONLYOFFICE_DSN not set — skipping SQL store integration test")
|
|
}
|
|
store, err := NewPGStore(cfg)
|
|
if err != nil {
|
|
t.Fatalf("NewPGStore: %v", err)
|
|
}
|
|
t.Cleanup(func() { _ = store.Close() })
|
|
t.Logf("sql store backend: %s", store.Name())
|
|
ctx := context.Background()
|
|
|
|
if err := testPGStoreReadOnly(ctx, store); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
fileID := strings.TrimSpace(os.Getenv("ONLYOFFICE_PG_TEST_FILE_ID"))
|
|
folderID := strings.TrimSpace(os.Getenv("ONLYOFFICE_PG_TEST_FOLDER_ID"))
|
|
if fileID == "" || folderID == "" {
|
|
t.Skip("ONLYOFFICE_PG_TEST_FILE_ID / ONLYOFFICE_PG_TEST_FOLDER_ID not set — skipping live comparison")
|
|
}
|
|
|
|
c := liveClient(t)
|
|
rest := c.Files()
|
|
|
|
dbFile, err := store.Stat(ctx, fileID)
|
|
if err != nil {
|
|
t.Fatalf("sql Stat(%s): %v", fileID, err)
|
|
}
|
|
restFile, err := rest.Stat(ctx, fileID)
|
|
if err != nil {
|
|
t.Fatalf("rest Stat(%s): %v", fileID, err)
|
|
}
|
|
if dbFile.Kind != File {
|
|
t.Errorf("sql kind = %v, want file", dbFile.Kind)
|
|
}
|
|
if dbFile.ID != restFile.ID || dbFile.Title != restFile.Title || dbFile.ParentID != restFile.ParentID {
|
|
t.Errorf("stat mismatch sql=%+v rest=%+v", dbFile, restFile)
|
|
}
|
|
// GetFile omits contentLength, so size is only comparable when REST has it.
|
|
if restFile.Size > 0 && dbFile.Size != restFile.Size {
|
|
t.Errorf("size sql=%d rest=%d", dbFile.Size, restFile.Size)
|
|
}
|
|
if d := dbFile.Modified.Sub(restFile.Modified); d > 2*time.Minute || d < -2*time.Minute {
|
|
t.Errorf("modified sql=%v rest=%v", dbFile.Modified, restFile.Modified)
|
|
}
|
|
|
|
list, err := store.List(ctx, folderID)
|
|
if err != nil {
|
|
t.Fatalf("sql List(%s): %v", folderID, err)
|
|
}
|
|
if entryByID(list, fileID) == nil {
|
|
t.Errorf("file %s not in sql List(%s)", fileID, folderID)
|
|
}
|
|
|
|
// Every file the REST layer can see in the folder must be in the SQL list
|
|
// (the SQL store sees more, so only assert this direction).
|
|
restList, err := rest.List(ctx, folderID)
|
|
if err != nil {
|
|
t.Fatalf("rest List(%s): %v", folderID, err)
|
|
}
|
|
dbIDs := make(map[string]bool, len(list))
|
|
for _, e := range list {
|
|
dbIDs[e.ID] = true
|
|
}
|
|
for _, e := range restList {
|
|
if e.Kind == File && !dbIDs[e.ID] {
|
|
t.Errorf("rest file %s (%q) missing from sql list", e.ID, e.Title)
|
|
}
|
|
}
|
|
|
|
var buf bytes.Buffer
|
|
n, err := store.Download(ctx, fileID, &buf)
|
|
if err != nil {
|
|
t.Fatalf("sql Download(%s): %v", fileID, err)
|
|
}
|
|
if n == 0 || n != dbFile.Size {
|
|
t.Errorf("sql Download = %d bytes, stat says %d", n, dbFile.Size)
|
|
}
|
|
if os.Getenv("MINIO_ACCESS_KEY") != "" && os.Getenv("MINIO_SECRET_KEY") != "" {
|
|
var restBuf bytes.Buffer
|
|
rn, err := rest.Download(ctx, fileID, &restBuf)
|
|
if err != nil {
|
|
t.Fatalf("rest Download(%s): %v", fileID, err)
|
|
}
|
|
if rn != n || !bytes.Equal(restBuf.Bytes(), buf.Bytes()) {
|
|
t.Errorf("download mismatch sql=%d rest=%d bytes", n, rn)
|
|
}
|
|
} else {
|
|
t.Log("MINIO_ACCESS_KEY/MINIO_SECRET_KEY not set — REST download cross-check skipped")
|
|
}
|
|
}
|
|
|
|
// testPGStoreReadOnly asserts that every write method returns ErrReadOnly.
|
|
func testPGStoreReadOnly(ctx context.Context, s *pgStore) error {
|
|
if _, err := s.CreateFolder(ctx, "1", "x"); !errors.Is(err, ErrReadOnly) {
|
|
return errors.New("CreateFolder did not return ErrReadOnly")
|
|
}
|
|
if _, err := s.Upload(ctx, "1", "x", strings.NewReader("x")); !errors.Is(err, ErrReadOnly) {
|
|
return errors.New("Upload did not return ErrReadOnly")
|
|
}
|
|
if err := s.Move(ctx, []string{"1"}, "2"); !errors.Is(err, ErrReadOnly) {
|
|
return errors.New("Move did not return ErrReadOnly")
|
|
}
|
|
if err := s.Copy(ctx, []string{"1"}, "2"); !errors.Is(err, ErrReadOnly) {
|
|
return errors.New("Copy did not return ErrReadOnly")
|
|
}
|
|
if err := s.Rename(ctx, "1", "x"); !errors.Is(err, ErrReadOnly) {
|
|
return errors.New("Rename did not return ErrReadOnly")
|
|
}
|
|
if err := s.Delete(ctx, []string{"1"}); !errors.Is(err, ErrReadOnly) {
|
|
return errors.New("Delete did not return ErrReadOnly")
|
|
}
|
|
return nil
|
|
}
|