feat(files): SQL file backend via Client.FileStore/SQLFileStore + live MySQL integration (#55)
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 stable) (pull_request) Successful in 24s
Tests / Test (Go 1.25) (pull_request) Successful in 26s
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 stable) (pull_request) Successful in 24s
Tests / Test (Go 1.25) (pull_request) Successful in 26s
This commit is contained in:
+96
-11
@@ -93,6 +93,14 @@ func TestIntegrationPGStore(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// Download reads the object store, not the database, so it only runs with
|
||||
// the MinIO credentials configured (the portal's S3 layout). Without them
|
||||
// the DSN-only assertions above still prove the SQL reads.
|
||||
if os.Getenv("MINIO_ACCESS_KEY") == "" || os.Getenv("MINIO_SECRET_KEY") == "" {
|
||||
t.Log("MINIO_ACCESS_KEY/MINIO_SECRET_KEY not set — SQL download cross-check skipped")
|
||||
return
|
||||
}
|
||||
|
||||
var buf bytes.Buffer
|
||||
n, err := store.Download(ctx, fileID, &buf)
|
||||
if err != nil {
|
||||
@@ -101,17 +109,94 @@ func TestIntegrationPGStore(t *testing.T) {
|
||||
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")
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
// TestIntegrationSQLFacade proves that reads are served by the SQL store when
|
||||
// it is part of the file client, not by REST. It needs ONLYOFFICE_DSN plus
|
||||
// ONLYOFFICE_PG_TEST_FILE_ID / ONLYOFFICE_PG_TEST_FOLDER_ID and the usual REST
|
||||
// credentials (for the cross-check). Every Entry served by SQL carries
|
||||
// Provider "mysql"; REST entries carry "rest", so the provider is the proof of
|
||||
// which backend answered.
|
||||
func TestIntegrationSQLFacade(t *testing.T) {
|
||||
cfg := PGConfigFromEnv()
|
||||
if strings.TrimSpace(cfg.DSN) == "" {
|
||||
t.Skip("ONLYOFFICE_DSN not set — skipping SQL facade integration test")
|
||||
}
|
||||
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 SQL facade integration test")
|
||||
}
|
||||
c := liveClient(t)
|
||||
ctx := context.Background()
|
||||
|
||||
sqlStore, err := c.SQLFileStore()
|
||||
if err != nil {
|
||||
t.Fatalf("SQLFileStore: %v", err)
|
||||
}
|
||||
if closer, ok := sqlStore.(interface{ Close() error }); ok {
|
||||
t.Cleanup(func() { _ = closer.Close() })
|
||||
}
|
||||
if sqlStore.Name() == ProviderREST {
|
||||
t.Fatalf("SQLFileStore returned REST")
|
||||
}
|
||||
|
||||
// Direct constructor: Client.FileStore("pg") must not be REST either.
|
||||
direct := c.FileStore("pg")
|
||||
if direct == nil || direct.Name() == ProviderREST {
|
||||
t.Fatalf("FileStore(\"pg\") = %v, want SQL backend", direct)
|
||||
}
|
||||
if closer, ok := direct.(interface{ Close() error }); ok {
|
||||
t.Cleanup(func() { _ = closer.Close() })
|
||||
}
|
||||
|
||||
f := c.Files()
|
||||
f.RegisterStore(ProviderPG, sqlStore)
|
||||
if got := f.Read().Name(); got != sqlStore.Name() {
|
||||
t.Fatalf("facade read backend = %q, want %q (SQL)", got, sqlStore.Name())
|
||||
}
|
||||
|
||||
got, err := f.Stat(ctx, fileID)
|
||||
if err != nil {
|
||||
t.Fatalf("facade Stat(%s): %v", fileID, err)
|
||||
}
|
||||
if got.Provider != ProviderMySQL {
|
||||
t.Errorf("facade Stat provider = %q, want %q (SQL, not REST)", got.Provider, ProviderMySQL)
|
||||
}
|
||||
want, err := c.FileStore(ProviderREST).Stat(ctx, fileID)
|
||||
if err != nil {
|
||||
t.Fatalf("rest Stat(%s): %v", fileID, err)
|
||||
}
|
||||
if got.ID != want.ID || got.Title != want.Title || got.ParentID != want.ParentID {
|
||||
t.Errorf("facade SQL stat %+v != REST %+v", got, want)
|
||||
}
|
||||
|
||||
list, err := f.List(ctx, folderID)
|
||||
if err != nil {
|
||||
t.Fatalf("facade List(%s): %v", folderID, err)
|
||||
}
|
||||
entry := entryByID(list, fileID)
|
||||
if entry == nil {
|
||||
t.Fatalf("file %s not in facade List(%s)", fileID, folderID)
|
||||
}
|
||||
if entry.Provider != ProviderMySQL {
|
||||
t.Errorf("facade List provider = %q, want %q", entry.Provider, ProviderMySQL)
|
||||
}
|
||||
|
||||
d, err := direct.Stat(ctx, fileID)
|
||||
if err != nil {
|
||||
t.Fatalf("FileStore(\"pg\").Stat(%s): %v", fileID, err)
|
||||
}
|
||||
if d.Provider != ProviderMySQL {
|
||||
t.Errorf("FileStore(\"pg\") provider = %q, want %q", d.Provider, ProviderMySQL)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user