156 lines
5.1 KiB
Go
156 lines
5.1 KiB
Go
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)
|
|
}
|
|
}
|