178 lines
5.8 KiB
Go
178 lines
5.8 KiB
Go
package configread
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"os"
|
|
"path/filepath"
|
|
"reflect"
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
const (
|
|
mockDispatcherID = "c046b893-8628-4589-ae50-619d049248a6"
|
|
mockTaskID = "task-mock"
|
|
mockTenantID = "tenant-id-mock"
|
|
mockTenantKey = "tenant-mock"
|
|
mockTestSecret = "test-secret"
|
|
)
|
|
|
|
func TestClientReadTaskRequestsAndValidatesThreeConfigEndpoints(t *testing.T) {
|
|
fixtures := validConfigFixtures(t)
|
|
var got []string
|
|
mux := http.NewServeMux()
|
|
for path, body := range fixtures {
|
|
path, body := path, body
|
|
mux.HandleFunc(path, func(w http.ResponseWriter, r *http.Request) {
|
|
got = append(got, r.Method+" "+r.URL.RequestURI())
|
|
if r.Method != http.MethodGet {
|
|
t.Errorf("method = %q, want GET", r.Method)
|
|
}
|
|
if r.Header.Get(dispatcherIDHeader) != mockDispatcherID {
|
|
t.Errorf("dispatcher ID header = %q", r.Header.Get(dispatcherIDHeader))
|
|
}
|
|
if r.Header.Get(dispatcherSecretHeader) != mockTestSecret {
|
|
t.Errorf("dispatcher secret header = %q", r.Header.Get(dispatcherSecretHeader))
|
|
}
|
|
w.Header().Set("Content-Type", "application/json")
|
|
_, _ = w.Write(body)
|
|
})
|
|
}
|
|
server := httptest.NewServer(mux)
|
|
defer server.Close()
|
|
|
|
client := newMockClient(t, server)
|
|
snapshot, err := client.ReadTask(context.Background(), mockTaskID, mockTenantID)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if len(snapshot.SIP) == 0 || len(snapshot.Task) == 0 || len(snapshot.TenantQuota) == 0 {
|
|
t.Fatalf("incomplete config snapshot: %+v", snapshot)
|
|
}
|
|
if snapshot.TaskID != mockTaskID || snapshot.TenantID != mockTenantID || snapshot.TenantKey != mockTenantKey {
|
|
t.Fatalf("snapshot identity = %q/%q/%q", snapshot.TaskID, snapshot.TenantID, snapshot.TenantKey)
|
|
}
|
|
want := []string{
|
|
"GET /internal/v1/dispatcher/sip",
|
|
"GET /internal/v1/dispatcher/task/" + mockTaskID,
|
|
"GET /internal/v1/dispatcher/tenant/" + mockTenantID + "/quota",
|
|
}
|
|
if !reflect.DeepEqual(got, want) {
|
|
t.Fatalf("requests = %#v, want %#v", got, want)
|
|
}
|
|
}
|
|
|
|
func TestClientReadTaskRejectsSchemaInvalidResponse(t *testing.T) {
|
|
fixtures := validConfigFixtures(t)
|
|
fixtures[configReadPath+"/sip"] = readConfigFixture(t, "config-read-invalid-extra-property-v0.1.json")
|
|
server := configFixtureServer(t, fixtures)
|
|
defer server.Close()
|
|
if _, err := newMockClient(t, server).ReadTask(context.Background(), mockTaskID, mockTenantID); err == nil {
|
|
t.Fatal("ReadTask accepted a response rejected by the frozen Schema")
|
|
}
|
|
}
|
|
|
|
func TestClientReadTaskRejectsIdentityMismatch(t *testing.T) {
|
|
cases := []struct {
|
|
name string
|
|
path string
|
|
old string
|
|
new string
|
|
}{
|
|
{"task id", configReadPath + "/task/" + mockTaskID, `"task_id": "task-mock"`, `"task_id": "task-other"`},
|
|
{"quota tenant key", configReadPath + "/tenant/" + mockTenantID + "/quota", `"tenant_key": "tenant-mock"`, `"tenant_key": "tenant-other"`},
|
|
}
|
|
for _, tc := range cases {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
fixtures := validConfigFixtures(t)
|
|
fixtures[tc.path] = replaceFixtureText(t, fixtures[tc.path], tc.old, tc.new)
|
|
server := configFixtureServer(t, fixtures)
|
|
defer server.Close()
|
|
if _, err := newMockClient(t, server).ReadTask(context.Background(), mockTaskID, mockTenantID); err == nil {
|
|
t.Fatal("ReadTask accepted an identity mismatch")
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestClientReadTaskReturnsStructuredHTTPFailure(t *testing.T) {
|
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
|
|
w.Header().Set("Content-Type", "application/json")
|
|
w.WriteHeader(http.StatusServiceUnavailable)
|
|
_, _ = fmt.Fprint(w, `{"schema_version":"config-read.v0.1","resource":"error","error":{"code":"service_unavailable","message":"temporarily unavailable"}}`)
|
|
}))
|
|
defer server.Close()
|
|
|
|
_, err := newMockClient(t, server).ReadTask(context.Background(), mockTaskID, mockTenantID)
|
|
var statusErr *HTTPError
|
|
if !errors.As(err, &statusErr) {
|
|
t.Fatalf("error = %v, want *HTTPError", err)
|
|
}
|
|
if statusErr.StatusCode != http.StatusServiceUnavailable || statusErr.Code != "service_unavailable" {
|
|
t.Fatalf("HTTP error = %+v", statusErr)
|
|
}
|
|
}
|
|
|
|
func marshalDiscoveryResponse(t *testing.T, response map[string]any) []byte {
|
|
t.Helper()
|
|
body, err := json.Marshal(response)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
return body
|
|
}
|
|
|
|
func newMockClient(t *testing.T, server *httptest.Server) *Client {
|
|
t.Helper()
|
|
client, err := NewClient(server.URL, mockDispatcherID, mockTestSecret, server.Client())
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
return client
|
|
}
|
|
|
|
func validConfigFixtures(t *testing.T) map[string][]byte {
|
|
t.Helper()
|
|
return map[string][]byte{
|
|
configReadPath + "/sip": readConfigFixture(t, "config-read-sip-v0.1.json"),
|
|
configReadPath + "/tasks": readConfigFixture(t, "task-discovery-snapshot-v0.2.json"),
|
|
configReadPath + "/task/" + mockTaskID: readConfigFixture(t, "config-read-task-v0.1.json"),
|
|
configReadPath + "/tenant/" + mockTenantID + "/quota": readConfigFixture(t, "config-read-tenant-quota-v0.1.json"),
|
|
}
|
|
}
|
|
|
|
func configFixtureServer(t *testing.T, fixtures map[string][]byte) *httptest.Server {
|
|
t.Helper()
|
|
return httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
body, ok := fixtures[r.URL.Path]
|
|
if !ok {
|
|
http.NotFound(w, r)
|
|
return
|
|
}
|
|
w.Header().Set("Content-Type", "application/json")
|
|
_, _ = w.Write(body)
|
|
}))
|
|
}
|
|
|
|
func readConfigFixture(t *testing.T, name string) []byte {
|
|
t.Helper()
|
|
body, err := os.ReadFile(filepath.Join("..", "..", "docs", "contracts", "examples", name))
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
return body
|
|
}
|
|
|
|
func replaceFixtureText(t *testing.T, body []byte, old, replacement string) []byte {
|
|
t.Helper()
|
|
if !strings.Contains(string(body), old) {
|
|
t.Fatalf("fixture does not contain %q", old)
|
|
}
|
|
return []byte(strings.Replace(string(body), old, replacement, 1))
|
|
}
|