229 lines
6.2 KiB
Go
229 lines
6.2 KiB
Go
package httpapi
|
|
|
|
import (
|
|
"crypto/ed25519"
|
|
"encoding/base64"
|
|
"encoding/json"
|
|
"io"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
"evobgp/internal/jobs"
|
|
"evobgp/internal/signing"
|
|
)
|
|
|
|
const testBundleSeed = "0101010101010101010101010101010101010101010101010101010101010101"
|
|
|
|
func TestAPIRefreshApplyJobsBundle(t *testing.T) {
|
|
srv, err := New(Options{
|
|
InsecureDev: true,
|
|
SeedDemo: true,
|
|
BundleSeedHex: testBundleSeed,
|
|
})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
tenant, modCDN, modIP, rev, speaker := srv.Store().DemoIDs()
|
|
srv.apiKeys = parseAPIKeysSpec("nodekey|" + tenant + "|node,opkey|" + tenant + "|operator")
|
|
|
|
ts := httptest.NewServer(srv.Handler())
|
|
defer ts.Close()
|
|
client := ts.Client()
|
|
base := ts.URL
|
|
|
|
t.Run("prometheus metrics", func(t *testing.T) {
|
|
req, _ := http.NewRequest(http.MethodGet, base+"/metrics", nil)
|
|
resp, err := client.Do(req)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer resp.Body.Close()
|
|
if resp.StatusCode != http.StatusOK {
|
|
b, _ := io.ReadAll(resp.Body)
|
|
t.Fatalf("status %d: %s", resp.StatusCode, b)
|
|
}
|
|
raw, _ := io.ReadAll(resp.Body)
|
|
s := string(raw)
|
|
for _, needle := range []string{
|
|
"evobgp_materialized_prefixes_max",
|
|
"evobgp_bgp_peers_configured_total",
|
|
"evobgp_http_requests_total", // incremented by this scrape request
|
|
} {
|
|
if !strings.Contains(s, needle) {
|
|
t.Fatalf("metrics body missing %q", needle)
|
|
}
|
|
}
|
|
})
|
|
|
|
t.Run("refresh IP_RANGES no op", func(t *testing.T) {
|
|
req, _ := http.NewRequest(http.MethodPost, base+"/v1/modules/"+modIP+"/refresh", nil)
|
|
req.Header.Set("Authorization", "Bearer opkey")
|
|
resp, err := client.Do(req)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer resp.Body.Close()
|
|
if resp.StatusCode != http.StatusNoContent {
|
|
b, _ := io.ReadAll(resp.Body)
|
|
t.Fatalf("status %d: %s", resp.StatusCode, b)
|
|
}
|
|
})
|
|
|
|
t.Run("refresh CDN queues job", func(t *testing.T) {
|
|
req, _ := http.NewRequest(http.MethodPost, base+"/v1/modules/"+modCDN+"/refresh", nil)
|
|
req.Header.Set("Authorization", "Bearer opkey")
|
|
resp, err := client.Do(req)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer resp.Body.Close()
|
|
if resp.StatusCode != http.StatusAccepted {
|
|
b, _ := io.ReadAll(resp.Body)
|
|
t.Fatalf("status %d: %s", resp.StatusCode, b)
|
|
}
|
|
var body struct {
|
|
JobID string `json:"job_id"`
|
|
}
|
|
if err := json.NewDecoder(resp.Body).Decode(&body); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
waitJob(t, client, base, "opkey", body.JobID)
|
|
})
|
|
|
|
t.Run("preview revision", func(t *testing.T) {
|
|
req, _ := http.NewRequest(http.MethodGet, base+"/v1/revisions/"+rev+"/preview", nil)
|
|
req.Header.Set("Authorization", "Bearer opkey")
|
|
resp, err := client.Do(req)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer resp.Body.Close()
|
|
if resp.StatusCode != http.StatusOK {
|
|
b, _ := io.ReadAll(resp.Body)
|
|
t.Fatalf("status %d: %s", resp.StatusCode, b)
|
|
}
|
|
})
|
|
|
|
t.Run("list modules peers speakers", func(t *testing.T) {
|
|
for _, path := range []string{"/v1/modules", "/v1/peers", "/v1/speakers"} {
|
|
req, _ := http.NewRequest(http.MethodGet, base+path, nil)
|
|
req.Header.Set("Authorization", "Bearer opkey")
|
|
resp, err := client.Do(req)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
b, _ := io.ReadAll(resp.Body)
|
|
resp.Body.Close()
|
|
if resp.StatusCode != http.StatusOK {
|
|
t.Fatalf("%s status %d: %s", path, resp.StatusCode, b)
|
|
}
|
|
var body struct {
|
|
Items []map[string]any `json:"items"`
|
|
}
|
|
if err := json.Unmarshal(b, &body); err != nil {
|
|
t.Fatalf("%s json: %v", path, err)
|
|
}
|
|
if len(body.Items) < 1 {
|
|
t.Fatalf("%s expected items", path)
|
|
}
|
|
}
|
|
})
|
|
|
|
t.Run("rollback queues job", func(t *testing.T) {
|
|
req, _ := http.NewRequest(http.MethodPost, base+"/v1/revisions/"+rev+"/rollback", nil)
|
|
req.Header.Set("Authorization", "Bearer opkey")
|
|
resp, err := client.Do(req)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer resp.Body.Close()
|
|
if resp.StatusCode != http.StatusAccepted {
|
|
b, _ := io.ReadAll(resp.Body)
|
|
t.Fatalf("status %d: %s", resp.StatusCode, b)
|
|
}
|
|
var body struct {
|
|
JobID string `json:"job_id"`
|
|
}
|
|
_ = json.NewDecoder(resp.Body).Decode(&body)
|
|
waitJob(t, client, base, "opkey", body.JobID)
|
|
})
|
|
|
|
t.Run("apply all speakers", func(t *testing.T) {
|
|
req, _ := http.NewRequest(http.MethodPost, base+"/v1/apply", strings.NewReader(`{"revision_id":"`+rev+`"}`))
|
|
req.Header.Set("Authorization", "Bearer opkey")
|
|
req.Header.Set("Content-Type", "application/json")
|
|
resp, err := client.Do(req)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer resp.Body.Close()
|
|
if resp.StatusCode != http.StatusAccepted {
|
|
b, _ := io.ReadAll(resp.Body)
|
|
t.Fatalf("status %d: %s", resp.StatusCode, b)
|
|
}
|
|
var body struct {
|
|
JobID string `json:"job_id"`
|
|
}
|
|
_ = json.NewDecoder(resp.Body).Decode(&body)
|
|
waitJob(t, client, base, "opkey", body.JobID)
|
|
})
|
|
|
|
pubB64 := srv.BundlePublicKeyBase64()
|
|
pubBytes, err := base64.StdEncoding.DecodeString(pubB64)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
t.Run("node bundle roundtrip verify", func(t *testing.T) {
|
|
req, _ := http.NewRequest(http.MethodGet, base+"/v1/speakers/"+speaker+"/bundle/"+rev, nil)
|
|
req.Header.Set("Authorization", "Bearer nodekey")
|
|
resp, err := client.Do(req)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer resp.Body.Close()
|
|
if resp.StatusCode != http.StatusOK {
|
|
b, _ := io.ReadAll(resp.Body)
|
|
t.Fatalf("status %d: %s", resp.StatusCode, b)
|
|
}
|
|
raw, err := io.ReadAll(resp.Body)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
_, err = signing.VerifyGzippedTar(raw, ed25519.PublicKey(pubBytes))
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
})
|
|
}
|
|
|
|
func waitJob(t *testing.T, client *http.Client, base, token, jobID string) {
|
|
t.Helper()
|
|
deadline := time.Now().Add(2 * time.Second)
|
|
for time.Now().Before(deadline) {
|
|
req, _ := http.NewRequest(http.MethodGet, base+"/v1/jobs/"+jobID, nil)
|
|
req.Header.Set("Authorization", "Bearer "+token)
|
|
resp, err := client.Do(req)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
b, _ := io.ReadAll(resp.Body)
|
|
resp.Body.Close()
|
|
var body struct {
|
|
Status string `json:"status"`
|
|
}
|
|
_ = json.Unmarshal(b, &body)
|
|
if body.Status == jobs.StatusSucceeded || body.Status == jobs.StatusFailed {
|
|
if body.Status != jobs.StatusSucceeded {
|
|
t.Fatalf("job %s status %s", jobID, body.Status)
|
|
}
|
|
return
|
|
}
|
|
time.Sleep(5 * time.Millisecond)
|
|
}
|
|
t.Fatalf("job %s did not complete", jobID)
|
|
}
|