| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156 |
- package api
- import (
- "bytes"
- "context"
- "encoding/json"
- "io"
- "mime/multipart"
- "net/http"
- "net/http/httptest"
- "os"
- "path/filepath"
- "testing"
- "debtik/internal/auth"
- "debtik/internal/backup"
- )
- func TestBackupAPIHandlers(t *testing.T) {
- tempDir := t.TempDir()
- debtikDir := filepath.Join(tempDir, "debtik")
- netDir := filepath.Join(tempDir, "network")
- storeDir := filepath.Join(tempDir, "backups")
- _ = os.MkdirAll(debtikDir, 0755)
- _ = os.MkdirAll(netDir, 0755)
- _ = os.WriteFile(filepath.Join(debtikDir, "metadata.json"), []byte(`{"router": "test"}`), 0644)
- _ = os.WriteFile(filepath.Join(netDir, "interfaces.cfg"), []byte(`auto lo`), 0644)
- backupMgr := backup.NewManager(debtikDir, netDir, storeDir, nil)
- s := &Server{
- backupMgr: backupMgr,
- mux: http.NewServeMux(),
- fsHandler: http.NotFoundHandler(),
- }
- s.registerRoutes()
- // 1. Test Backup Export (Download)
- req := httptest.NewRequest(http.MethodGet, "/api/system/backup/export?comment=APITest", nil)
- // Context with full admin role
- ctx := context.WithValue(req.Context(), sessionContextKey, &auth.Session{
- Username: "admin",
- Role: auth.RoleFull,
- })
- req = req.WithContext(ctx)
- rec := httptest.NewRecorder()
- s.mux.ServeHTTP(rec, req)
- if rec.Code != http.StatusOK {
- t.Fatalf("Expected 200 OK from backup export, got %d: %s", rec.Code, rec.Body.String())
- }
- if rec.Header().Get("Content-Type") != "application/zip" {
- t.Errorf("Expected application/zip Content-Type, got %s", rec.Header().Get("Content-Type"))
- }
- zipBytes := rec.Body.Bytes()
- if len(zipBytes) == 0 {
- t.Fatal("Exported zip is empty")
- }
- // 2. Test Backup Inspect via multipart upload
- bodyBuf := new(bytes.Buffer)
- mpWriter := multipart.NewWriter(bodyBuf)
- part, err := mpWriter.CreateFormFile("backup", "test-backup.zip")
- if err != nil {
- t.Fatal(err)
- }
- _, _ = io.Copy(part, bytes.NewReader(zipBytes))
- _ = mpWriter.Close()
- inspectReq := httptest.NewRequest(http.MethodPost, "/api/system/backup/inspect", bodyBuf)
- inspectReq.Header.Set("Content-Type", mpWriter.FormDataContentType())
- inspectReq = inspectReq.WithContext(ctx)
- inspectRec := httptest.NewRecorder()
- s.mux.ServeHTTP(inspectRec, inspectReq)
- if inspectRec.Code != http.StatusOK {
- t.Fatalf("Expected 200 OK from backup inspect, got %d: %s", inspectRec.Code, inspectRec.Body.String())
- }
- var inspectResp struct {
- Success bool `json:"success"`
- Manifest *backup.Manifest `json:"manifest"`
- }
- if err := json.Unmarshal(inspectRec.Body.Bytes(), &inspectResp); err != nil {
- t.Fatalf("Failed unmarshaling inspect response: %v", err)
- }
- if !inspectResp.Success || inspectResp.Manifest == nil {
- t.Fatal("Expected success and manifest in inspect response")
- }
- if inspectResp.Manifest.Comment != "APITest" {
- t.Errorf("Manifest comment mismatch: %s != APITest", inspectResp.Manifest.Comment)
- }
- // 3. Test Backup Restore
- restoreBuf := new(bytes.Buffer)
- mpWriter2 := multipart.NewWriter(restoreBuf)
- part2, _ := mpWriter2.CreateFormFile("backup", "restore-backup.zip")
- _, _ = io.Copy(part2, bytes.NewReader(zipBytes))
- _ = mpWriter2.Close()
- restoreReq := httptest.NewRequest(http.MethodPost, "/api/system/backup/restore", restoreBuf)
- restoreReq.Header.Set("Content-Type", mpWriter2.FormDataContentType())
- restoreReq = restoreReq.WithContext(ctx)
- restoreRec := httptest.NewRecorder()
- s.mux.ServeHTTP(restoreRec, restoreReq)
- if restoreRec.Code != http.StatusOK {
- t.Fatalf("Expected 200 OK from backup restore, got %d: %s", restoreRec.Code, restoreRec.Body.String())
- }
- var restoreRes backup.RestoreResult
- if err := json.Unmarshal(restoreRec.Body.Bytes(), &restoreRes); err != nil {
- t.Fatalf("Failed unmarshaling restore response: %v", err)
- }
- if !restoreRes.Success {
- t.Error("Expected restore success true")
- }
- // 4. Test Backup List (should contain pre-restore safety backup)
- listReq := httptest.NewRequest(http.MethodGet, "/api/system/backups", nil)
- listReq = listReq.WithContext(ctx)
- listRec := httptest.NewRecorder()
- s.mux.ServeHTTP(listRec, listReq)
- if listRec.Code != http.StatusOK {
- t.Fatalf("Expected 200 OK from backup list, got %d: %s", listRec.Code, listRec.Body.String())
- }
- var backupsList []backup.BackupInfo
- if err := json.Unmarshal(listRec.Body.Bytes(), &backupsList); err != nil {
- t.Fatalf("Failed unmarshaling backup list: %v", err)
- }
- if len(backupsList) == 0 {
- t.Error("Expected at least 1 backup in list")
- }
- // 5. Test Forbidden for Read-Only user
- roCtx := context.WithValue(req.Context(), sessionContextKey, &auth.Session{
- Username: "readonly-user",
- Role: auth.RoleRead,
- })
- roReq := httptest.NewRequest(http.MethodGet, "/api/system/backup/export", nil)
- roReq = roReq.WithContext(roCtx)
- roRec := httptest.NewRecorder()
- s.mux.ServeHTTP(roRec, roReq)
- if roRec.Code != http.StatusForbidden {
- t.Errorf("Expected 403 Forbidden for read-only user, got %d", roRec.Code)
- }
- }
|