feat: BreakPilot PWA - Full codebase (clean push without large binaries)
Some checks failed
Tests / Go Tests (push) Has been cancelled
Tests / Python Tests (push) Has been cancelled
Tests / Integration Tests (push) Has been cancelled
Tests / Go Lint (push) Has been cancelled
Tests / Python Lint (push) Has been cancelled
Tests / Security Scan (push) Has been cancelled
Tests / All Checks Passed (push) Has been cancelled
Security Scanning / Secret Scanning (push) Has been cancelled
Security Scanning / Dependency Vulnerability Scan (push) Has been cancelled
Security Scanning / Go Security Scan (push) Has been cancelled
Security Scanning / Python Security Scan (push) Has been cancelled
Security Scanning / Node.js Security Scan (push) Has been cancelled
Security Scanning / Docker Image Security (push) Has been cancelled
Security Scanning / Security Summary (push) Has been cancelled
CI/CD Pipeline / Go Tests (push) Has been cancelled
CI/CD Pipeline / Python Tests (push) Has been cancelled
CI/CD Pipeline / Website Tests (push) Has been cancelled
CI/CD Pipeline / Linting (push) Has been cancelled
CI/CD Pipeline / Security Scan (push) Has been cancelled
CI/CD Pipeline / Docker Build & Push (push) Has been cancelled
CI/CD Pipeline / Integration Tests (push) Has been cancelled
CI/CD Pipeline / Deploy to Staging (push) Has been cancelled
CI/CD Pipeline / Deploy to Production (push) Has been cancelled
CI/CD Pipeline / CI Summary (push) Has been cancelled
ci/woodpecker/manual/build-ci-image Pipeline was successful
ci/woodpecker/manual/main Pipeline failed
Some checks failed
Tests / Go Tests (push) Has been cancelled
Tests / Python Tests (push) Has been cancelled
Tests / Integration Tests (push) Has been cancelled
Tests / Go Lint (push) Has been cancelled
Tests / Python Lint (push) Has been cancelled
Tests / Security Scan (push) Has been cancelled
Tests / All Checks Passed (push) Has been cancelled
Security Scanning / Secret Scanning (push) Has been cancelled
Security Scanning / Dependency Vulnerability Scan (push) Has been cancelled
Security Scanning / Go Security Scan (push) Has been cancelled
Security Scanning / Python Security Scan (push) Has been cancelled
Security Scanning / Node.js Security Scan (push) Has been cancelled
Security Scanning / Docker Image Security (push) Has been cancelled
Security Scanning / Security Summary (push) Has been cancelled
CI/CD Pipeline / Go Tests (push) Has been cancelled
CI/CD Pipeline / Python Tests (push) Has been cancelled
CI/CD Pipeline / Website Tests (push) Has been cancelled
CI/CD Pipeline / Linting (push) Has been cancelled
CI/CD Pipeline / Security Scan (push) Has been cancelled
CI/CD Pipeline / Docker Build & Push (push) Has been cancelled
CI/CD Pipeline / Integration Tests (push) Has been cancelled
CI/CD Pipeline / Deploy to Staging (push) Has been cancelled
CI/CD Pipeline / Deploy to Production (push) Has been cancelled
CI/CD Pipeline / CI Summary (push) Has been cancelled
ci/woodpecker/manual/build-ci-image Pipeline was successful
ci/woodpecker/manual/main Pipeline failed
All services: admin-v2, studio-v2, website, ai-compliance-sdk, consent-service, klausur-service, voice-service, and infrastructure. Large PDFs and compiled binaries excluded via .gitignore.
This commit is contained in:
645
edu-search-service/internal/api/handlers/handlers_test.go
Normal file
645
edu-search-service/internal/api/handlers/handlers_test.go
Normal file
@@ -0,0 +1,645 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
func init() {
|
||||
gin.SetMode(gin.TestMode)
|
||||
}
|
||||
|
||||
// setupTestRouter creates a test router with the handler
|
||||
func setupTestRouter(h *Handler, apiKey string) *gin.Engine {
|
||||
router := gin.New()
|
||||
SetupRoutes(router, h, apiKey)
|
||||
return router
|
||||
}
|
||||
|
||||
// setupTestSeedStore creates a test seed store
|
||||
func setupTestSeedStore(t *testing.T) string {
|
||||
t.Helper()
|
||||
dir := t.TempDir()
|
||||
|
||||
// Initialize global seed store
|
||||
err := InitSeedStore(dir)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to initialize seed store: %v", err)
|
||||
}
|
||||
|
||||
return dir
|
||||
}
|
||||
|
||||
func TestHealthEndpoint(t *testing.T) {
|
||||
// Health endpoint requires indexClient for health check
|
||||
// This test verifies the route is set up correctly
|
||||
// A full integration test would need a mock OpenSearch client
|
||||
t.Skip("Skipping: requires mock indexer client for full test")
|
||||
}
|
||||
|
||||
func TestAuthMiddleware_NoAuth(t *testing.T) {
|
||||
h := &Handler{}
|
||||
router := setupTestRouter(h, "test-api-key")
|
||||
|
||||
// Request without auth header
|
||||
req, _ := http.NewRequest("POST", "/v1/search", bytes.NewBufferString(`{"q":"test"}`))
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
w := httptest.NewRecorder()
|
||||
router.ServeHTTP(w, req)
|
||||
|
||||
if w.Code != http.StatusUnauthorized {
|
||||
t.Errorf("Expected status 401, got %d", w.Code)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAuthMiddleware_InvalidFormat(t *testing.T) {
|
||||
h := &Handler{}
|
||||
router := setupTestRouter(h, "test-api-key")
|
||||
|
||||
// Request with wrong auth format
|
||||
req, _ := http.NewRequest("POST", "/v1/search", bytes.NewBufferString(`{"q":"test"}`))
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
req.Header.Set("Authorization", "Basic dGVzdDp0ZXN0") // Basic auth instead of Bearer
|
||||
w := httptest.NewRecorder()
|
||||
router.ServeHTTP(w, req)
|
||||
|
||||
if w.Code != http.StatusUnauthorized {
|
||||
t.Errorf("Expected status 401, got %d", w.Code)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAuthMiddleware_InvalidKey(t *testing.T) {
|
||||
h := &Handler{}
|
||||
router := setupTestRouter(h, "test-api-key")
|
||||
|
||||
// Request with wrong API key
|
||||
req, _ := http.NewRequest("POST", "/v1/search", bytes.NewBufferString(`{"q":"test"}`))
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
req.Header.Set("Authorization", "Bearer wrong-key")
|
||||
w := httptest.NewRecorder()
|
||||
router.ServeHTTP(w, req)
|
||||
|
||||
if w.Code != http.StatusUnauthorized {
|
||||
t.Errorf("Expected status 401, got %d", w.Code)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAuthMiddleware_ValidKey(t *testing.T) {
|
||||
h := &Handler{}
|
||||
router := setupTestRouter(h, "test-api-key")
|
||||
|
||||
// Request with correct API key (search will fail due to no search service, but auth should pass)
|
||||
req, _ := http.NewRequest("GET", "/v1/document?doc_id=test", nil)
|
||||
req.Header.Set("Authorization", "Bearer test-api-key")
|
||||
w := httptest.NewRecorder()
|
||||
router.ServeHTTP(w, req)
|
||||
|
||||
// Auth should pass, endpoint returns 501 (not implemented)
|
||||
if w.Code == http.StatusUnauthorized {
|
||||
t.Error("Expected auth to pass, got 401")
|
||||
}
|
||||
}
|
||||
|
||||
func TestAuthMiddleware_HealthNoAuth(t *testing.T) {
|
||||
// Health endpoint requires indexClient for health check
|
||||
// Skipping because route calls h.indexClient.Health() which panics with nil
|
||||
t.Skip("Skipping: requires mock indexer client for full test")
|
||||
}
|
||||
|
||||
func TestGetDocument_MissingDocID(t *testing.T) {
|
||||
h := &Handler{}
|
||||
router := setupTestRouter(h, "test-key")
|
||||
|
||||
req, _ := http.NewRequest("GET", "/v1/document", nil)
|
||||
req.Header.Set("Authorization", "Bearer test-key")
|
||||
w := httptest.NewRecorder()
|
||||
router.ServeHTTP(w, req)
|
||||
|
||||
if w.Code != http.StatusBadRequest {
|
||||
t.Errorf("Expected status 400, got %d", w.Code)
|
||||
}
|
||||
}
|
||||
|
||||
// Admin Handler Tests
|
||||
|
||||
func TestSeedStore_InitAndLoad(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
|
||||
// First initialization should create default seeds
|
||||
err := InitSeedStore(dir)
|
||||
if err != nil {
|
||||
t.Fatalf("InitSeedStore failed: %v", err)
|
||||
}
|
||||
|
||||
// Check that seeds file was created
|
||||
seedsFile := filepath.Join(dir, "seeds.json")
|
||||
if _, err := os.Stat(seedsFile); os.IsNotExist(err) {
|
||||
t.Error("seeds.json was not created")
|
||||
}
|
||||
|
||||
// Check that default seeds were loaded
|
||||
seeds := seedStore.GetAllSeeds()
|
||||
if len(seeds) == 0 {
|
||||
t.Error("Expected default seeds to be loaded")
|
||||
}
|
||||
}
|
||||
|
||||
func TestSeedStore_CreateSeed(t *testing.T) {
|
||||
setupTestSeedStore(t)
|
||||
|
||||
newSeed := SeedURL{
|
||||
URL: "https://test.example.com",
|
||||
Name: "Test Seed",
|
||||
Category: "test",
|
||||
Description: "A test seed",
|
||||
TrustBoost: 0.5,
|
||||
Enabled: true,
|
||||
}
|
||||
|
||||
created, err := seedStore.CreateSeed(newSeed)
|
||||
if err != nil {
|
||||
t.Fatalf("CreateSeed failed: %v", err)
|
||||
}
|
||||
|
||||
if created.ID == "" {
|
||||
t.Error("Expected generated ID")
|
||||
}
|
||||
if created.URL != newSeed.URL {
|
||||
t.Errorf("Expected URL %q, got %q", newSeed.URL, created.URL)
|
||||
}
|
||||
if created.CreatedAt.IsZero() {
|
||||
t.Error("Expected CreatedAt to be set")
|
||||
}
|
||||
}
|
||||
|
||||
func TestSeedStore_GetSeed(t *testing.T) {
|
||||
setupTestSeedStore(t)
|
||||
|
||||
// Create a seed first
|
||||
newSeed := SeedURL{
|
||||
URL: "https://get-test.example.com",
|
||||
Name: "Get Test",
|
||||
Category: "test",
|
||||
}
|
||||
created, _ := seedStore.CreateSeed(newSeed)
|
||||
|
||||
// Get the seed
|
||||
retrieved, found := seedStore.GetSeed(created.ID)
|
||||
if !found {
|
||||
t.Fatal("Seed not found")
|
||||
}
|
||||
|
||||
if retrieved.URL != newSeed.URL {
|
||||
t.Errorf("Expected URL %q, got %q", newSeed.URL, retrieved.URL)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSeedStore_GetSeed_NotFound(t *testing.T) {
|
||||
setupTestSeedStore(t)
|
||||
|
||||
_, found := seedStore.GetSeed("nonexistent-id")
|
||||
if found {
|
||||
t.Error("Expected seed not to be found")
|
||||
}
|
||||
}
|
||||
|
||||
func TestSeedStore_UpdateSeed(t *testing.T) {
|
||||
setupTestSeedStore(t)
|
||||
|
||||
// Create a seed first
|
||||
original := SeedURL{
|
||||
URL: "https://update-test.example.com",
|
||||
Name: "Original Name",
|
||||
Category: "test",
|
||||
Enabled: true,
|
||||
}
|
||||
created, _ := seedStore.CreateSeed(original)
|
||||
|
||||
// Update the seed
|
||||
updates := SeedURL{
|
||||
Name: "Updated Name",
|
||||
TrustBoost: 0.75,
|
||||
Enabled: false,
|
||||
}
|
||||
|
||||
updated, found, err := seedStore.UpdateSeed(created.ID, updates)
|
||||
if err != nil {
|
||||
t.Fatalf("UpdateSeed failed: %v", err)
|
||||
}
|
||||
if !found {
|
||||
t.Fatal("Seed not found for update")
|
||||
}
|
||||
|
||||
if updated.Name != "Updated Name" {
|
||||
t.Errorf("Expected name 'Updated Name', got %q", updated.Name)
|
||||
}
|
||||
if updated.TrustBoost != 0.75 {
|
||||
t.Errorf("Expected TrustBoost 0.75, got %f", updated.TrustBoost)
|
||||
}
|
||||
if updated.Enabled != false {
|
||||
t.Error("Expected Enabled to be false")
|
||||
}
|
||||
// URL should remain unchanged since we didn't provide it
|
||||
if updated.URL != original.URL {
|
||||
t.Errorf("URL should remain unchanged, expected %q, got %q", original.URL, updated.URL)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSeedStore_UpdateSeed_NotFound(t *testing.T) {
|
||||
setupTestSeedStore(t)
|
||||
|
||||
updates := SeedURL{Name: "New Name"}
|
||||
_, found, err := seedStore.UpdateSeed("nonexistent-id", updates)
|
||||
if err != nil {
|
||||
t.Fatalf("Unexpected error: %v", err)
|
||||
}
|
||||
if found {
|
||||
t.Error("Expected seed not to be found")
|
||||
}
|
||||
}
|
||||
|
||||
func TestSeedStore_DeleteSeed(t *testing.T) {
|
||||
setupTestSeedStore(t)
|
||||
|
||||
// Create a seed first
|
||||
newSeed := SeedURL{
|
||||
URL: "https://delete-test.example.com",
|
||||
Name: "Delete Test",
|
||||
Category: "test",
|
||||
}
|
||||
created, _ := seedStore.CreateSeed(newSeed)
|
||||
|
||||
// Delete the seed
|
||||
deleted := seedStore.DeleteSeed(created.ID)
|
||||
if !deleted {
|
||||
t.Error("Expected delete to succeed")
|
||||
}
|
||||
|
||||
// Verify it's gone
|
||||
_, found := seedStore.GetSeed(created.ID)
|
||||
if found {
|
||||
t.Error("Seed should have been deleted")
|
||||
}
|
||||
}
|
||||
|
||||
func TestSeedStore_DeleteSeed_NotFound(t *testing.T) {
|
||||
setupTestSeedStore(t)
|
||||
|
||||
deleted := seedStore.DeleteSeed("nonexistent-id")
|
||||
if deleted {
|
||||
t.Error("Expected delete to return false for nonexistent seed")
|
||||
}
|
||||
}
|
||||
|
||||
func TestSeedStore_Persistence(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
|
||||
// Create and populate seed store
|
||||
err := InitSeedStore(dir)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
newSeed := SeedURL{
|
||||
URL: "https://persist-test.example.com",
|
||||
Name: "Persistence Test",
|
||||
Category: "test",
|
||||
}
|
||||
created, err := seedStore.CreateSeed(newSeed)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Re-initialize from the same directory
|
||||
seedStore = nil
|
||||
err = InitSeedStore(dir)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Check if the seed persisted
|
||||
retrieved, found := seedStore.GetSeed(created.ID)
|
||||
if !found {
|
||||
t.Error("Seed should have persisted")
|
||||
}
|
||||
if retrieved.URL != newSeed.URL {
|
||||
t.Errorf("Persisted seed URL mismatch: expected %q, got %q", newSeed.URL, retrieved.URL)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAdminGetSeeds(t *testing.T) {
|
||||
dir := setupTestSeedStore(t)
|
||||
|
||||
h := &Handler{}
|
||||
router := gin.New()
|
||||
SetupRoutes(router, h, "test-key")
|
||||
|
||||
// Initialize seed store for the test
|
||||
InitSeedStore(dir)
|
||||
|
||||
req, _ := http.NewRequest("GET", "/v1/admin/seeds", nil)
|
||||
req.Header.Set("Authorization", "Bearer test-key")
|
||||
w := httptest.NewRecorder()
|
||||
router.ServeHTTP(w, req)
|
||||
|
||||
if w.Code != http.StatusOK {
|
||||
t.Errorf("Expected status 200, got %d", w.Code)
|
||||
}
|
||||
|
||||
var seeds []SeedURL
|
||||
if err := json.Unmarshal(w.Body.Bytes(), &seeds); err != nil {
|
||||
t.Fatalf("Failed to parse response: %v", err)
|
||||
}
|
||||
|
||||
// Should have default seeds
|
||||
if len(seeds) == 0 {
|
||||
t.Error("Expected seeds to be returned")
|
||||
}
|
||||
}
|
||||
|
||||
func TestAdminCreateSeed(t *testing.T) {
|
||||
dir := setupTestSeedStore(t)
|
||||
|
||||
h := &Handler{}
|
||||
router := gin.New()
|
||||
SetupRoutes(router, h, "test-key")
|
||||
InitSeedStore(dir)
|
||||
|
||||
newSeed := map[string]interface{}{
|
||||
"url": "https://new-seed.example.com",
|
||||
"name": "New Seed",
|
||||
"category": "test",
|
||||
"description": "Test description",
|
||||
"trustBoost": 0.5,
|
||||
"enabled": true,
|
||||
}
|
||||
|
||||
body, _ := json.Marshal(newSeed)
|
||||
req, _ := http.NewRequest("POST", "/v1/admin/seeds", bytes.NewBuffer(body))
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
req.Header.Set("Authorization", "Bearer test-key")
|
||||
w := httptest.NewRecorder()
|
||||
router.ServeHTTP(w, req)
|
||||
|
||||
if w.Code != http.StatusCreated {
|
||||
t.Errorf("Expected status 201, got %d: %s", w.Code, w.Body.String())
|
||||
}
|
||||
|
||||
var created SeedURL
|
||||
if err := json.Unmarshal(w.Body.Bytes(), &created); err != nil {
|
||||
t.Fatalf("Failed to parse response: %v", err)
|
||||
}
|
||||
|
||||
if created.ID == "" {
|
||||
t.Error("Expected ID to be generated")
|
||||
}
|
||||
if created.URL != "https://new-seed.example.com" {
|
||||
t.Errorf("Expected URL to match, got %q", created.URL)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAdminCreateSeed_MissingURL(t *testing.T) {
|
||||
dir := setupTestSeedStore(t)
|
||||
|
||||
h := &Handler{}
|
||||
router := gin.New()
|
||||
SetupRoutes(router, h, "test-key")
|
||||
InitSeedStore(dir)
|
||||
|
||||
newSeed := map[string]interface{}{
|
||||
"name": "No URL Seed",
|
||||
"category": "test",
|
||||
}
|
||||
|
||||
body, _ := json.Marshal(newSeed)
|
||||
req, _ := http.NewRequest("POST", "/v1/admin/seeds", bytes.NewBuffer(body))
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
req.Header.Set("Authorization", "Bearer test-key")
|
||||
w := httptest.NewRecorder()
|
||||
router.ServeHTTP(w, req)
|
||||
|
||||
if w.Code != http.StatusBadRequest {
|
||||
t.Errorf("Expected status 400 for missing URL, got %d", w.Code)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAdminUpdateSeed(t *testing.T) {
|
||||
dir := setupTestSeedStore(t)
|
||||
|
||||
h := &Handler{}
|
||||
router := gin.New()
|
||||
SetupRoutes(router, h, "test-key")
|
||||
InitSeedStore(dir)
|
||||
|
||||
// Create a seed first
|
||||
newSeed := SeedURL{
|
||||
URL: "https://update-api-test.example.com",
|
||||
Name: "API Update Test",
|
||||
Category: "test",
|
||||
}
|
||||
created, _ := seedStore.CreateSeed(newSeed)
|
||||
|
||||
// Update via API
|
||||
updates := map[string]interface{}{
|
||||
"name": "Updated via API",
|
||||
"trustBoost": 0.8,
|
||||
}
|
||||
|
||||
body, _ := json.Marshal(updates)
|
||||
req, _ := http.NewRequest("PUT", "/v1/admin/seeds/"+created.ID, bytes.NewBuffer(body))
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
req.Header.Set("Authorization", "Bearer test-key")
|
||||
w := httptest.NewRecorder()
|
||||
router.ServeHTTP(w, req)
|
||||
|
||||
if w.Code != http.StatusOK {
|
||||
t.Errorf("Expected status 200, got %d: %s", w.Code, w.Body.String())
|
||||
}
|
||||
|
||||
var updated SeedURL
|
||||
if err := json.Unmarshal(w.Body.Bytes(), &updated); err != nil {
|
||||
t.Fatalf("Failed to parse response: %v", err)
|
||||
}
|
||||
|
||||
if updated.Name != "Updated via API" {
|
||||
t.Errorf("Expected name 'Updated via API', got %q", updated.Name)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAdminDeleteSeed(t *testing.T) {
|
||||
dir := setupTestSeedStore(t)
|
||||
|
||||
h := &Handler{}
|
||||
router := gin.New()
|
||||
SetupRoutes(router, h, "test-key")
|
||||
InitSeedStore(dir)
|
||||
|
||||
// Create a seed first
|
||||
newSeed := SeedURL{
|
||||
URL: "https://delete-api-test.example.com",
|
||||
Name: "API Delete Test",
|
||||
Category: "test",
|
||||
}
|
||||
created, _ := seedStore.CreateSeed(newSeed)
|
||||
|
||||
// Delete via API
|
||||
req, _ := http.NewRequest("DELETE", "/v1/admin/seeds/"+created.ID, nil)
|
||||
req.Header.Set("Authorization", "Bearer test-key")
|
||||
w := httptest.NewRecorder()
|
||||
router.ServeHTTP(w, req)
|
||||
|
||||
if w.Code != http.StatusOK {
|
||||
t.Errorf("Expected status 200, got %d", w.Code)
|
||||
}
|
||||
|
||||
// Verify it's deleted
|
||||
_, found := seedStore.GetSeed(created.ID)
|
||||
if found {
|
||||
t.Error("Seed should have been deleted")
|
||||
}
|
||||
}
|
||||
|
||||
func TestAdminDeleteSeed_NotFound(t *testing.T) {
|
||||
dir := setupTestSeedStore(t)
|
||||
|
||||
h := &Handler{}
|
||||
router := gin.New()
|
||||
SetupRoutes(router, h, "test-key")
|
||||
InitSeedStore(dir)
|
||||
|
||||
req, _ := http.NewRequest("DELETE", "/v1/admin/seeds/nonexistent-id", nil)
|
||||
req.Header.Set("Authorization", "Bearer test-key")
|
||||
w := httptest.NewRecorder()
|
||||
router.ServeHTTP(w, req)
|
||||
|
||||
if w.Code != http.StatusNotFound {
|
||||
t.Errorf("Expected status 404, got %d", w.Code)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAdminGetStats(t *testing.T) {
|
||||
dir := setupTestSeedStore(t)
|
||||
|
||||
h := &Handler{}
|
||||
router := gin.New()
|
||||
SetupRoutes(router, h, "test-key")
|
||||
InitSeedStore(dir)
|
||||
|
||||
req, _ := http.NewRequest("GET", "/v1/admin/stats", nil)
|
||||
req.Header.Set("Authorization", "Bearer test-key")
|
||||
w := httptest.NewRecorder()
|
||||
router.ServeHTTP(w, req)
|
||||
|
||||
if w.Code != http.StatusOK {
|
||||
t.Errorf("Expected status 200, got %d", w.Code)
|
||||
}
|
||||
|
||||
var stats CrawlStats
|
||||
if err := json.Unmarshal(w.Body.Bytes(), &stats); err != nil {
|
||||
t.Fatalf("Failed to parse response: %v", err)
|
||||
}
|
||||
|
||||
// Check that stats structure is populated
|
||||
if stats.CrawlStatus == "" {
|
||||
t.Error("Expected CrawlStatus to be set")
|
||||
}
|
||||
if stats.DocumentsPerCategory == nil {
|
||||
t.Error("Expected DocumentsPerCategory to be set")
|
||||
}
|
||||
}
|
||||
|
||||
func TestAdminStartCrawl(t *testing.T) {
|
||||
dir := setupTestSeedStore(t)
|
||||
|
||||
h := &Handler{}
|
||||
router := gin.New()
|
||||
SetupRoutes(router, h, "test-key")
|
||||
InitSeedStore(dir)
|
||||
|
||||
// Reset crawl status
|
||||
crawlStatus = "idle"
|
||||
|
||||
req, _ := http.NewRequest("POST", "/v1/admin/crawl/start", nil)
|
||||
req.Header.Set("Authorization", "Bearer test-key")
|
||||
w := httptest.NewRecorder()
|
||||
router.ServeHTTP(w, req)
|
||||
|
||||
if w.Code != http.StatusAccepted {
|
||||
t.Errorf("Expected status 202, got %d: %s", w.Code, w.Body.String())
|
||||
}
|
||||
|
||||
var response map[string]interface{}
|
||||
if err := json.Unmarshal(w.Body.Bytes(), &response); err != nil {
|
||||
t.Fatalf("Failed to parse response: %v", err)
|
||||
}
|
||||
|
||||
if response["status"] != "started" {
|
||||
t.Errorf("Expected status 'started', got %v", response["status"])
|
||||
}
|
||||
}
|
||||
|
||||
func TestAdminStartCrawl_AlreadyRunning(t *testing.T) {
|
||||
dir := setupTestSeedStore(t)
|
||||
|
||||
h := &Handler{}
|
||||
router := gin.New()
|
||||
SetupRoutes(router, h, "test-key")
|
||||
InitSeedStore(dir)
|
||||
|
||||
// Set crawl status to running
|
||||
crawlStatus = "running"
|
||||
|
||||
req, _ := http.NewRequest("POST", "/v1/admin/crawl/start", nil)
|
||||
req.Header.Set("Authorization", "Bearer test-key")
|
||||
w := httptest.NewRecorder()
|
||||
router.ServeHTTP(w, req)
|
||||
|
||||
if w.Code != http.StatusConflict {
|
||||
t.Errorf("Expected status 409, got %d", w.Code)
|
||||
}
|
||||
|
||||
// Reset for other tests
|
||||
crawlStatus = "idle"
|
||||
}
|
||||
|
||||
func TestConcurrentSeedAccess(t *testing.T) {
|
||||
setupTestSeedStore(t)
|
||||
|
||||
// Test concurrent reads and writes
|
||||
done := make(chan bool, 10)
|
||||
|
||||
// Concurrent readers
|
||||
for i := 0; i < 5; i++ {
|
||||
go func() {
|
||||
seedStore.GetAllSeeds()
|
||||
done <- true
|
||||
}()
|
||||
}
|
||||
|
||||
// Concurrent writers
|
||||
for i := 0; i < 5; i++ {
|
||||
go func(n int) {
|
||||
seed := SeedURL{
|
||||
URL: "https://concurrent-" + string(rune('A'+n)) + ".example.com",
|
||||
Name: "Concurrent Test",
|
||||
Category: "test",
|
||||
}
|
||||
seedStore.CreateSeed(seed)
|
||||
done <- true
|
||||
}(i)
|
||||
}
|
||||
|
||||
// Wait for all goroutines
|
||||
for i := 0; i < 10; i++ {
|
||||
<-done
|
||||
}
|
||||
|
||||
// If we get here without deadlock or race, test passes
|
||||
}
|
||||
Reference in New Issue
Block a user