Implement local DB migrations and archived configuration lifecycle
This commit is contained in:
173
cmd/qfs/versioning_api_test.go
Normal file
173
cmd/qfs/versioning_api_test.go
Normal file
@@ -0,0 +1,173 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"git.mchus.pro/mchus/quoteforge/internal/config"
|
||||
"git.mchus.pro/mchus/quoteforge/internal/db"
|
||||
"git.mchus.pro/mchus/quoteforge/internal/localdb"
|
||||
"git.mchus.pro/mchus/quoteforge/internal/models"
|
||||
"git.mchus.pro/mchus/quoteforge/internal/services"
|
||||
syncsvc "git.mchus.pro/mchus/quoteforge/internal/services/sync"
|
||||
)
|
||||
|
||||
func TestConfigurationVersioningAPI(t *testing.T) {
|
||||
moveToRepoRoot(t)
|
||||
|
||||
local, connMgr, configService := newAPITestStack(t)
|
||||
_ = local
|
||||
|
||||
created, err := configService.Create("tester", &services.CreateConfigRequest{
|
||||
Name: "api-v1",
|
||||
Items: models.ConfigItems{{LotName: "CPU_API", Quantity: 1, UnitPrice: 1000}},
|
||||
ServerCount: 1,
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("create config: %v", err)
|
||||
}
|
||||
if _, err := configService.RenameNoAuth(created.UUID, "api-v2"); err != nil {
|
||||
t.Fatalf("rename config: %v", err)
|
||||
}
|
||||
|
||||
cfg := &config.Config{}
|
||||
setConfigDefaults(cfg)
|
||||
router, _, err := setupRouter(cfg, local, connMgr, nil, "tester")
|
||||
if err != nil {
|
||||
t.Fatalf("setup router: %v", err)
|
||||
}
|
||||
|
||||
// list versions happy path
|
||||
listReq := httptest.NewRequest(http.MethodGet, "/api/configs/"+created.UUID+"/versions?limit=10&offset=0", nil)
|
||||
listRec := httptest.NewRecorder()
|
||||
router.ServeHTTP(listRec, listReq)
|
||||
if listRec.Code != http.StatusOK {
|
||||
t.Fatalf("list versions status=%d body=%s", listRec.Code, listRec.Body.String())
|
||||
}
|
||||
|
||||
// get version happy path
|
||||
getReq := httptest.NewRequest(http.MethodGet, "/api/configs/"+created.UUID+"/versions/1", nil)
|
||||
getRec := httptest.NewRecorder()
|
||||
router.ServeHTTP(getRec, getReq)
|
||||
if getRec.Code != http.StatusOK {
|
||||
t.Fatalf("get version status=%d body=%s", getRec.Code, getRec.Body.String())
|
||||
}
|
||||
|
||||
// rollback happy path
|
||||
body := []byte(`{"target_version":1,"note":"api rollback"}`)
|
||||
rbReq := httptest.NewRequest(http.MethodPost, "/api/configs/"+created.UUID+"/rollback", bytes.NewReader(body))
|
||||
rbReq.Header.Set("Content-Type", "application/json")
|
||||
rbRec := httptest.NewRecorder()
|
||||
router.ServeHTTP(rbRec, rbReq)
|
||||
if rbRec.Code != http.StatusOK {
|
||||
t.Fatalf("rollback status=%d body=%s", rbRec.Code, rbRec.Body.String())
|
||||
}
|
||||
|
||||
var rbResp struct {
|
||||
Message string `json:"message"`
|
||||
CurrentVersion struct {
|
||||
VersionNo int `json:"version_no"`
|
||||
} `json:"current_version"`
|
||||
}
|
||||
if err := json.Unmarshal(rbRec.Body.Bytes(), &rbResp); err != nil {
|
||||
t.Fatalf("unmarshal rollback response: %v", err)
|
||||
}
|
||||
if rbResp.Message == "" || rbResp.CurrentVersion.VersionNo != 3 {
|
||||
t.Fatalf("unexpected rollback response: %+v", rbResp)
|
||||
}
|
||||
|
||||
// 404: version missing
|
||||
notFoundReq := httptest.NewRequest(http.MethodGet, "/api/configs/"+created.UUID+"/versions/999", nil)
|
||||
notFoundRec := httptest.NewRecorder()
|
||||
router.ServeHTTP(notFoundRec, notFoundReq)
|
||||
if notFoundRec.Code != http.StatusNotFound {
|
||||
t.Fatalf("expected 404 for missing version, got %d", notFoundRec.Code)
|
||||
}
|
||||
|
||||
// 400: invalid version number
|
||||
invalidReq := httptest.NewRequest(http.MethodGet, "/api/configs/"+created.UUID+"/versions/abc", nil)
|
||||
invalidRec := httptest.NewRecorder()
|
||||
router.ServeHTTP(invalidRec, invalidReq)
|
||||
if invalidRec.Code != http.StatusBadRequest {
|
||||
t.Fatalf("expected 400 for invalid version, got %d", invalidRec.Code)
|
||||
}
|
||||
|
||||
// 400: rollback invalid target_version
|
||||
badRollbackReq := httptest.NewRequest(http.MethodPost, "/api/configs/"+created.UUID+"/rollback", bytes.NewReader([]byte(`{"target_version":0}`)))
|
||||
badRollbackReq.Header.Set("Content-Type", "application/json")
|
||||
badRollbackRec := httptest.NewRecorder()
|
||||
router.ServeHTTP(badRollbackRec, badRollbackReq)
|
||||
if badRollbackRec.Code != http.StatusBadRequest {
|
||||
t.Fatalf("expected 400 for invalid rollback target, got %d", badRollbackRec.Code)
|
||||
}
|
||||
|
||||
// archive + reactivate flow
|
||||
delReq := httptest.NewRequest(http.MethodDelete, "/api/configs/"+created.UUID, nil)
|
||||
delRec := httptest.NewRecorder()
|
||||
router.ServeHTTP(delRec, delReq)
|
||||
if delRec.Code != http.StatusOK {
|
||||
t.Fatalf("archive status=%d body=%s", delRec.Code, delRec.Body.String())
|
||||
}
|
||||
|
||||
archivedListReq := httptest.NewRequest(http.MethodGet, "/api/configs?status=archived&page=1&per_page=20", nil)
|
||||
archivedListRec := httptest.NewRecorder()
|
||||
router.ServeHTTP(archivedListRec, archivedListReq)
|
||||
if archivedListRec.Code != http.StatusOK {
|
||||
t.Fatalf("archived list status=%d body=%s", archivedListRec.Code, archivedListRec.Body.String())
|
||||
}
|
||||
|
||||
reactivateReq := httptest.NewRequest(http.MethodPost, "/api/configs/"+created.UUID+"/reactivate", nil)
|
||||
reactivateRec := httptest.NewRecorder()
|
||||
router.ServeHTTP(reactivateRec, reactivateReq)
|
||||
if reactivateRec.Code != http.StatusOK {
|
||||
t.Fatalf("reactivate status=%d body=%s", reactivateRec.Code, reactivateRec.Body.String())
|
||||
}
|
||||
|
||||
activeListReq := httptest.NewRequest(http.MethodGet, "/api/configs?status=active&page=1&per_page=20", nil)
|
||||
activeListRec := httptest.NewRecorder()
|
||||
router.ServeHTTP(activeListRec, activeListReq)
|
||||
if activeListRec.Code != http.StatusOK {
|
||||
t.Fatalf("active list status=%d body=%s", activeListRec.Code, activeListRec.Body.String())
|
||||
}
|
||||
}
|
||||
|
||||
func newAPITestStack(t *testing.T) (*localdb.LocalDB, *db.ConnectionManager, *services.LocalConfigurationService) {
|
||||
t.Helper()
|
||||
|
||||
localPath := filepath.Join(t.TempDir(), "api.db")
|
||||
local, err := localdb.New(localPath)
|
||||
if err != nil {
|
||||
t.Fatalf("init local db: %v", err)
|
||||
}
|
||||
t.Cleanup(func() { _ = local.Close() })
|
||||
|
||||
connMgr := db.NewConnectionManager(local)
|
||||
syncService := syncsvc.NewService(connMgr, local)
|
||||
configService := services.NewLocalConfigurationService(
|
||||
local,
|
||||
syncService,
|
||||
&services.QuoteService{},
|
||||
func() bool { return false },
|
||||
)
|
||||
return local, connMgr, configService
|
||||
}
|
||||
|
||||
func moveToRepoRoot(t *testing.T) {
|
||||
t.Helper()
|
||||
wd, err := os.Getwd()
|
||||
if err != nil {
|
||||
t.Fatalf("getwd: %v", err)
|
||||
}
|
||||
root := filepath.Clean(filepath.Join(wd, "..", ".."))
|
||||
if err := os.Chdir(root); err != nil {
|
||||
t.Fatalf("chdir repo root: %v", err)
|
||||
}
|
||||
t.Cleanup(func() {
|
||||
_ = os.Chdir(wd)
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user