admin: submissions page, download endpoint, uploads search by app_id
Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,155 @@
|
||||
package admin
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
|
||||
"spark-mcp-go/internal/storage"
|
||||
"spark-mcp-go/internal/uploads"
|
||||
)
|
||||
|
||||
func newTestAdminSubmissions(t *testing.T) (*gin.Engine, *storage.SubmissionRepo, *storage.UploadRepo, *uploads.Store) {
|
||||
t.Helper()
|
||||
db, err := storage.Open(":memory:")
|
||||
if err != nil {
|
||||
t.Fatalf("open: %v", err)
|
||||
}
|
||||
t.Cleanup(func() { _ = db.Close() })
|
||||
|
||||
uploadStore, err := uploads.New(filepath.Join(t.TempDir(), "uploads"))
|
||||
if err != nil {
|
||||
t.Fatalf("new upload store: %v", err)
|
||||
}
|
||||
|
||||
r := gin.New()
|
||||
Mount(r, db.Clusters(), db.Uploads(), db.Submissions(), nil, &uploadStore, []string{"good-token"})
|
||||
return r, db.Submissions(), db.Uploads(), &uploadStore
|
||||
}
|
||||
|
||||
func TestSubmissionsWeb_RequiresAuth(t *testing.T) {
|
||||
r, _, _, _ := newTestAdminSubmissions(t)
|
||||
|
||||
w := doReq(t, r, "GET", "/admin/submissions", "", nil)
|
||||
if w.Code != http.StatusUnauthorized {
|
||||
t.Errorf("GET /admin/submissions without auth: got %d, want %d", w.Code, http.StatusUnauthorized)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSubmissionsWeb_WithAuth(t *testing.T) {
|
||||
r, _, _, _ := newTestAdminSubmissions(t)
|
||||
|
||||
w := doReq(t, r, "GET", "/admin/submissions", "good-token", nil)
|
||||
if w.Code != http.StatusOK {
|
||||
t.Fatalf("GET /admin/submissions with auth: got %d, want %d", w.Code, http.StatusOK)
|
||||
}
|
||||
ct := w.Header().Get("Content-Type")
|
||||
if !strings.Contains(ct, "text/html") {
|
||||
t.Errorf("Content-Type = %q, want text/html", ct)
|
||||
}
|
||||
body := w.Body.String()
|
||||
if !strings.Contains(body, "href=\"/admin/uploads\"") && !strings.Contains(body, "href='/admin/uploads'") {
|
||||
t.Errorf("body missing uploads nav link")
|
||||
}
|
||||
if !strings.Contains(body, "href=\"/admin/submissions\"") && !strings.Contains(body, "href='/admin/submissions'") {
|
||||
t.Errorf("body missing submissions nav link")
|
||||
}
|
||||
if !strings.Contains(body, "submissions-body") {
|
||||
t.Errorf("body missing submissions table")
|
||||
}
|
||||
}
|
||||
|
||||
func TestListSubmissions_SearchByAppID(t *testing.T) {
|
||||
r, subRepo, uploadRepo, _ := newTestAdminSubmissions(t)
|
||||
ctx := context.Background()
|
||||
|
||||
uploadedAt := time.Unix(0, time.Now().UnixNano())
|
||||
if err := uploadRepo.Create(ctx, "00000000000000000000000000000001", "alpha.py", 10, "deadbeef", uploadedAt); err != nil {
|
||||
t.Fatalf("create upload: %v", err)
|
||||
}
|
||||
if err := uploadRepo.Create(ctx, "00000000000000000000000000000002", "beta.py", 20, "cafebabe", uploadedAt.Add(time.Second)); err != nil {
|
||||
t.Fatalf("create upload: %v", err)
|
||||
}
|
||||
|
||||
submissions := []storage.Submission{
|
||||
{FileID: "00000000000000000000000000000001", AppID: "application_111_0001", TrackingURL: "http://a", ClusterID: "c1", ExitCode: 0, DurationMS: 100, SubmittedAt: time.Now()},
|
||||
{FileID: "00000000000000000000000000000002", AppID: "application_222_0001", TrackingURL: "http://b", ClusterID: "c1", ExitCode: 0, DurationMS: 100, SubmittedAt: time.Now().Add(time.Second)},
|
||||
}
|
||||
for _, s := range submissions {
|
||||
if err := subRepo.Create(ctx, s); err != nil {
|
||||
t.Fatalf("create submission: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
w := doReq(t, r, "GET", "/admin/submissions/api?search=application_111", "good-token", nil)
|
||||
if w.Code != http.StatusOK {
|
||||
t.Fatalf("search: got status %d: %s", w.Code, w.Body.String())
|
||||
}
|
||||
var list []map[string]any
|
||||
if err := json.Unmarshal(w.Body.Bytes(), &list); err != nil {
|
||||
t.Fatalf("unmarshal search: %v", err)
|
||||
}
|
||||
if len(list) != 1 {
|
||||
t.Errorf("search result: got %d, want 1", len(list))
|
||||
}
|
||||
if len(list) > 0 && list[0]["app_id"] != "application_111_0001" {
|
||||
t.Errorf("app_id=%v, want application_111_0001", list[0]["app_id"])
|
||||
}
|
||||
|
||||
w = doReq(t, r, "GET", "/admin/submissions/api?search=beta.py", "good-token", nil)
|
||||
if w.Code != http.StatusOK {
|
||||
t.Fatalf("search by name: got status %d: %s", w.Code, w.Body.String())
|
||||
}
|
||||
if err := json.Unmarshal(w.Body.Bytes(), &list); err != nil {
|
||||
t.Fatalf("unmarshal search: %v", err)
|
||||
}
|
||||
if len(list) != 1 {
|
||||
t.Errorf("search by name result: got %d, want 1", len(list))
|
||||
}
|
||||
}
|
||||
|
||||
func TestDownloadUpload(t *testing.T) {
|
||||
r, _, uploadRepo, store := newTestAdminSubmissions(t)
|
||||
ctx := context.Background()
|
||||
|
||||
id := "00000000000000000000000000000003"
|
||||
payload := []byte("hello payload")
|
||||
if err := uploadRepo.Create(ctx, id, "report.csv", int64(len(payload)), "feedface", time.Now()); err != nil {
|
||||
t.Fatalf("create upload: %v", err)
|
||||
}
|
||||
dataPath, err := store.AbsPath(id)
|
||||
if err != nil {
|
||||
t.Fatalf("abspath: %v", err)
|
||||
}
|
||||
if err := os.WriteFile(dataPath, payload, 0o640); err != nil {
|
||||
t.Fatalf("write data: %v", err)
|
||||
}
|
||||
|
||||
w := doReq(t, r, "GET", "/admin/uploads/"+id+"/download", "good-token", nil)
|
||||
if w.Code != http.StatusOK {
|
||||
t.Fatalf("download: got status %d: %s", w.Code, w.Body.String())
|
||||
}
|
||||
if string(w.Body.Bytes()) != string(payload) {
|
||||
t.Errorf("body = %q, want %q", w.Body.Bytes(), payload)
|
||||
}
|
||||
cd := w.Header().Get("Content-Disposition")
|
||||
if !strings.Contains(cd, `filename="report.csv"`) {
|
||||
t.Errorf("Content-Disposition = %q, want filename=\"report.csv\"", cd)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDownloadUpload_NotFound(t *testing.T) {
|
||||
r, _, _, _ := newTestAdminSubmissions(t)
|
||||
|
||||
w := doReq(t, r, "GET", "/admin/uploads/00000000000000000000000000000099/download", "good-token", nil)
|
||||
if w.Code != http.StatusNotFound {
|
||||
t.Errorf("download unknown: got %d, want %d", w.Code, http.StatusNotFound)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user