Files
rogee 13c31fc902
Build web service image / build (push) Successful in 1m53s
feat: add remote control plane and whitelist reads
2026-09-12 09:46:05 +08:00

98 lines
4.0 KiB
Go

package controlplane
import (
"encoding/json"
"net/http"
"net/http/httptest"
"path/filepath"
"testing"
"time"
)
func TestCancellationLeaseRenewalAndExpiredGeneration(t *testing.T) {
server, err := NewServer(ServerConfig{
DataFile: filepath.Join(t.TempDir(), "state.json"),
NodeTokens: map[string]string{"node-1": "node-secret"},
WebUsers: map[string]string{"admin": "web-secret"},
LeaseTTL: time.Minute,
})
if err != nil {
t.Fatal(err)
}
httpServer := httptest.NewServer(server.Handler())
defer httpServer.Close()
client := httpServer.Client()
login := doJSON(t, client, http.MethodPost, httpServer.URL+"/v1/auth/login", "", map[string]string{"username": "admin", "password": "web-secret"})
var session struct {
AccessToken string `json:"access_token"`
}
decodeBody(t, login, &session)
webAuth := "Bearer " + session.AccessToken
registration := NodeRegistration{NodeID: "node-1", AgentVersion: "test", ProtocolVersion: ProtocolVersion,
Accounts: []AccountSummary{{AccountID: "account-a", Active: true, Verified: true}}}
if response := doJSON(t, client, http.MethodPost, httpServer.URL+"/v1/nodes/register", "Bearer node-secret", registration); response.Code != http.StatusOK {
t.Fatal(response.Code)
}
create := func(key string) TaskSubmissionResponse {
response := doJSON(t, client, http.MethodPost, httpServer.URL+"/v1/tasks", webAuth, TaskSubmission{
NodeID: "node-1", AccountID: "account-a", Kind: "send-text", IdempotencyKey: key,
Payload: json.RawMessage(`{"target_id":"target","text":"text","confirmed":true}`),
})
var result TaskSubmissionResponse
decodeBody(t, response, &result)
return result
}
cancelled := create("cancel-before-ack")
poll := doJSON(t, client, http.MethodGet, httpServer.URL+"/v1/nodes/node-1/tasks?account_id=account-a", "Bearer node-secret", nil)
var batch TaskBatch
decodeBody(t, poll, &batch)
if len(batch.Tasks) != 1 {
t.Fatalf("expected one task, got %d", len(batch.Tasks))
}
lease := batch.Tasks[0]
cancel := doJSON(t, client, http.MethodPost, httpServer.URL+"/v1/tasks/"+cancelled.TaskID+"/cancel", webAuth, nil)
var cancelledTask Task
decodeBody(t, cancel, &cancelledTask)
if cancelledTask.Status != TaskPending || cancelledTask.CancelRequestedAt == nil {
t.Fatalf("cancel mutated primary state: %+v", cancelledTask)
}
ack := TaskAck{TaskID: lease.TaskID, AccountID: lease.AccountID, LeaseGeneration: lease.LeaseGeneration}
ackResponse := doJSON(t, client, http.MethodPost, httpServer.URL+"/v1/nodes/node-1/tasks/"+lease.TaskID+"/ack", "Bearer node-secret", ack)
var acked Task
decodeBody(t, ackResponse, &acked)
if acked.Status != TaskCancelled {
t.Fatalf("cancel was not confirmed at safe ack point: %+v", acked)
}
running := create("renew-and-stale")
poll = doJSON(t, client, http.MethodGet, httpServer.URL+"/v1/nodes/node-1/tasks?account_id=account-a", "Bearer node-secret", nil)
batch = TaskBatch{}
decodeBody(t, poll, &batch)
var runningLease Task
for _, task := range batch.Tasks {
if task.TaskID == running.TaskID {
runningLease = task
}
}
if runningLease.TaskID == "" {
t.Fatal("running task was not offered")
}
ack = TaskAck{TaskID: runningLease.TaskID, AccountID: runningLease.AccountID, LeaseGeneration: runningLease.LeaseGeneration}
for _, phase := range []string{"ack", "start", "renew"} {
response := doJSON(t, client, http.MethodPost, httpServer.URL+"/v1/nodes/node-1/tasks/"+runningLease.TaskID+"/"+phase, "Bearer node-secret", ack)
if response.Code != http.StatusOK {
t.Fatalf("%s status = %d: %s", phase, response.Code, response.Body.String())
}
}
stale := ack
stale.LeaseGeneration++
staleResponse := doJSON(t, client, http.MethodPost, httpServer.URL+"/v1/nodes/node-1/tasks/"+runningLease.TaskID+"/result", "Bearer node-secret", TaskResult{
TaskID: runningLease.TaskID, AccountID: runningLease.AccountID, LeaseGeneration: stale.LeaseGeneration, Status: TaskSucceeded, CorrelationID: "stale",
})
if staleResponse.Code != http.StatusConflict {
t.Fatalf("stale result status = %d", staleResponse.Code)
}
}