package api import ( "encoding/json" "net/http" "net/http/httptest" "strconv" "testing" "github.com/gin-gonic/gin" "vpn-platform/backend/internal/core" "vpn-platform/backend/internal/model" "vpn-platform/backend/internal/store" ) type stateTestProtocol struct{} func (stateTestProtocol) Name() string { return "state-test" } func (stateTestProtocol) DisplayName() string { return "state-test" } func (stateTestProtocol) Validate(cfg map[string]interface{}) (map[string]interface{}, error) { return cfg, nil } func (stateTestProtocol) Create(*model.Tunnel) error { return nil } func (stateTestProtocol) Start(*model.Tunnel) error { return nil } func (stateTestProtocol) Stop(*model.Tunnel) error { return nil } func (stateTestProtocol) Status(*model.Tunnel) (core.Status, error) { return core.Status{}, nil } func (stateTestProtocol) GenerateClientConfig(*model.Tunnel) (string, error) { return "", nil } func (stateTestProtocol) Delete(*model.Tunnel) error { return nil } func (stateTestProtocol) Available() (bool, string) { return true, "" } func TestStartStopDropsStaleRuntimeSnapshot(t *testing.T) { oldDB := store.DB t.Cleanup(func() { store.DB = oldDB }) if err := store.Init(t.TempDir()); err != nil { t.Fatal(err) } line := model.Tunnel{Name: "snapshot", Protocol: "state-test", State: "running", Config: "{}"} if err := store.DB.Create(&line).Error; err != nil { t.Fatal(err) } mgr := core.NewManager() mgr.Register(stateTestProtocol{}) s := NewServer(mgr) for _, tc := range []struct{ operation, oldState, wantState string }{ {"stop", "running", "stopped"}, {"start", "stopped", "running"}, } { mgr.SetStatus(line.ID, core.Status{State: tc.oldState}) w := httptest.NewRecorder() c, _ := gin.CreateTestContext(w) c.Params = gin.Params{{Key: "id", Value: strconv.FormatUint(uint64(line.ID), 10)}} if tc.operation == "stop" { s.stopTunnel(c) } else { s.startTunnel(c) } if w.Code != http.StatusOK { t.Fatalf("%s returned %d: %s", tc.operation, w.Code, w.Body.String()) } if _, ok := mgr.GetStatus(line.ID); ok { t.Fatalf("%s left the old runtime status cached", tc.operation) } listResponse := httptest.NewRecorder() listContext, _ := gin.CreateTestContext(listResponse) s.listTunnels(listContext) var rows []struct { State string `json:"state"` Runtime *core.Status `json:"runtime"` } if err := json.Unmarshal(listResponse.Body.Bytes(), &rows); err != nil { t.Fatal(err) } if len(rows) != 1 || rows[0].State != tc.wantState || rows[0].Runtime != nil { t.Fatalf("%s list returned stale state: %+v", tc.operation, rows) } } }