go-htmx-gsd #1

Merged
arthur merged 558 commits from go-htmx-gsd into main 2026-05-23 15:16:44 +00:00
3 changed files with 162 additions and 0 deletions
Showing only changes of commit 3327a4286d - Show all commits

View file

@ -17,5 +17,38 @@ services:
timeout: 5s
retries: 10
minio:
image: minio/minio:latest
container_name: xtablo-backend-minio
restart: unless-stopped
environment:
MINIO_ROOT_USER: minioadmin
MINIO_ROOT_PASSWORD: minioadmin
ports:
- "9000:9000" # S3 API
- "9001:9001" # Console UI
command: server /data --console-address ":9001"
volumes:
- minio_data:/data
healthcheck:
test: ["CMD", "mc", "ready", "local"]
interval: 5s
timeout: 5s
retries: 10
minio-init:
image: minio/mc:latest
depends_on:
minio:
condition: service_healthy
entrypoint: >
/bin/sh -c "
mc alias set local http://minio:9000 minioadmin minioadmin;
mc mb --ignore-existing local/xtablo-dev;
echo 'bucket ready';
"
restart: "no"
volumes:
postgres_data:
minio_data:

View file

@ -0,0 +1,51 @@
package files
import (
"context"
"os"
"testing"
)
// TestStoreImplementsFileStorer is a compile-time assertion that *Store
// satisfies the FileStorer interface.
func TestStoreImplementsFileStorer(t *testing.T) {
var _ FileStorer = (*Store)(nil)
}
// TestNewStore_SkipIfNoEndpoint verifies that NewStore returns a non-nil client
// when S3_ENDPOINT is configured. The test is skipped in CI / local dev unless
// a real MinIO instance is running and S3_ENDPOINT is set.
func TestNewStore_SkipIfNoEndpoint(t *testing.T) {
endpoint := os.Getenv("S3_ENDPOINT")
if endpoint == "" {
t.Skip("S3_ENDPOINT not set — skipping live NewStore test")
}
bucket := os.Getenv("S3_BUCKET")
if bucket == "" {
bucket = "xtablo-dev"
}
region := os.Getenv("S3_REGION")
if region == "" {
region = "us-east-1"
}
accessKey := os.Getenv("S3_ACCESS_KEY")
if accessKey == "" {
accessKey = "minioadmin"
}
secretKey := os.Getenv("S3_SECRET_KEY")
if secretKey == "" {
secretKey = "minioadmin"
}
store, err := NewStore(context.Background(), endpoint, bucket, region, accessKey, secretKey, true)
if err != nil {
t.Fatalf("NewStore: %v", err)
}
if store == nil {
t.Fatal("NewStore returned nil store without error")
}
if store.client == nil {
t.Fatal("NewStore returned store with nil S3 client")
}
}

View file

@ -0,0 +1,78 @@
package web
// handlers_files_test.go — Wave 0 RED test scaffold for FILE-01..06.
//
// All test functions call t.Skip("FILE handler tests: not yet implemented") so they
// compile but do NOT fail before Plan 02/03 implements the handlers and routes.
// This file is the RED baseline; Plan 02/03 turns it green.
//
// Pattern: mirrors handlers_tasks_test.go exactly — setupTestDB, loginUser,
// preInsertUser, getCSRFToken, and testCSRFKey are reused from the existing
// test files in the same package.
import (
"context"
"io"
"testing"
"backend/internal/files"
)
// stubbedFileStorer is a no-op FileStorer for test injection.
// Plan 02/03 will replace the Skip calls with real assertions that use this stub.
type stubbedFileStorer struct{}
func (s *stubbedFileStorer) Upload(_ context.Context, _ string, _ io.Reader) (string, int64, error) {
return "application/octet-stream", 0, nil
}
func (s *stubbedFileStorer) Delete(_ context.Context, _ string) error {
return nil
}
func (s *stubbedFileStorer) PresignDownload(_ context.Context, _ string) (string, error) {
return "https://example.com/presigned", nil
}
// Compile-time assertion: stubbedFileStorer satisfies the files.FileStorer interface.
var _ files.FileStorer = (*stubbedFileStorer)(nil)
// ---- TestFileUpload (FILE-01, FILE-02) ----
// TestFileUpload verifies that POST /tablos/{id}/files uploads a file,
// creates a DB row, and stores bytes in S3 (FILE-01 server-proxied upload, FILE-02).
func TestFileUpload(t *testing.T) {
t.Skip("FILE handler tests: not yet implemented")
}
// ---- TestFilesList (FILE-03) ----
// TestFilesList verifies that GET /tablos/{id}/files lists files with
// original filename and size, newest first (FILE-03).
func TestFilesList(t *testing.T) {
t.Skip("FILE handler tests: not yet implemented")
}
// ---- TestFileDownload (FILE-04) ----
// TestFileDownload verifies that GET /tablos/{id}/files/{file_id}/download
// returns a 302 redirect to a signed time-limited URL (FILE-04).
func TestFileDownload(t *testing.T) {
t.Skip("FILE handler tests: not yet implemented")
}
// ---- TestFileDelete (FILE-05) ----
// TestFileDelete verifies that POST /tablos/{id}/files/{file_id}/delete
// removes the DB row (and invokes S3 delete via the stub) (FILE-05).
func TestFileDelete(t *testing.T) {
t.Skip("FILE handler tests: not yet implemented")
}
// ---- TestFileOwnership (FILE-06) ----
// TestFileOwnership verifies that a non-owner gets 404 on
// GET /tablos/{id}/files, POST /tablos/{id}/files, and all file sub-routes (FILE-06).
func TestFileOwnership(t *testing.T) {
t.Skip("FILE handler tests: not yet implemented")
}