Publish route-proxy's contract as a Go module of its own: the route a consumer asks, its schema, the events the proxy says, what it keeps on the bus and its tools' arguments, so other modules use the contract by its version and the provisioner carries a route unread (issue 774, research 072 row 34)
This commit is contained in:
@@ -0,0 +1,85 @@
|
||||
package contract
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"reflect"
|
||||
"slices"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func limit(n int64) *int64 { return &n }
|
||||
|
||||
// A route is read strictly and whole (issue 774): what the contract names is admitted, what it does not, or a route
|
||||
// it cannot serve, is refused naming why; and the published schema names exactly the fields the decoder reads.
|
||||
func TestARouteIsReadStrictly(t *testing.T) {
|
||||
for _, c := range []struct {
|
||||
raw, refused string
|
||||
want Request
|
||||
}{
|
||||
{raw: `{"name":"app.example.org","internal-name":"app.anchor.internal","label":"app","endpoint":"web","port":8080}`,
|
||||
want: Request{Name: "app.example.org", InternalName: "app.anchor.internal", Label: "app", Endpoint: "web", Port: 8080}},
|
||||
{raw: `{"internal-name":"app.anchor.internal","port":443,"scheme":"https","insecure":true,"max-request-body":1048576}`,
|
||||
want: Request{InternalName: "app.anchor.internal", Port: 443, Scheme: "https", Insecure: true, MaxRequestBody: limit(1048576)}},
|
||||
{raw: `{"name":"git.example.org","path":"/api/internal","deny":true,"priority":100000}`,
|
||||
want: Request{Name: "git.example.org", Path: "/api/internal", Deny: true, Priority: 100000}},
|
||||
{raw: `{"name":"old.example.org","redirect":"https://new.example.org"}`,
|
||||
want: Request{Name: "old.example.org", Redirect: "https://new.example.org"}},
|
||||
{raw: `{"name":"admin.example.org","port":80,"auth":"/run/secrets/admin"}`,
|
||||
want: Request{Name: "admin.example.org", Port: 80, Auth: "/run/secrets/admin"}},
|
||||
{raw: `{"label":"app","port":8080}`, refused: "names nothing"},
|
||||
{raw: `{"name":"app.example.org"}`, refused: "gives no port"},
|
||||
{raw: `{"name":"app.example.org","port":70000}`, refused: "not a port"},
|
||||
{raw: `{"name":"app.example.org","port":80.5}`, refused: "not a route"},
|
||||
{raw: `{"name":"app.example.org","port":"80"}`, refused: "not a route"},
|
||||
{raw: `{"name":"app.example.org","port":80,"scheme":"ftp"}`, refused: "neither http nor https"},
|
||||
{raw: `{"name":"app.example.org","port":80,"max-request-body":-1}`, refused: "max-request-body"},
|
||||
{raw: `{"name":"app.example.org","port":80,"max-request-body":0}`, refused: "max-request-body"},
|
||||
{raw: `{"name":"app.example.org","port":80,"auth":"admin:$2y$05$abc"}`, refused: "credential"},
|
||||
{raw: `{"name":"app.example.org","port":80,"host":"x"}`, refused: "unknown field \"host\""},
|
||||
{raw: `{"name":"app.example.org","port":80} {}`, refused: "more than one"},
|
||||
} {
|
||||
got, err := DecodeRequest([]byte(c.raw))
|
||||
if c.refused != "" {
|
||||
if err == nil || !strings.Contains(err.Error(), c.refused) {
|
||||
t.Errorf("DecodeRequest(%s) = %+v, %v; want refused for %q", c.raw, got, err, c.refused)
|
||||
}
|
||||
continue
|
||||
}
|
||||
if err != nil || !reflect.DeepEqual(got, c.want) {
|
||||
t.Errorf("DecodeRequest(%s) = %+v, %v; want %+v", c.raw, got, err, c.want)
|
||||
continue
|
||||
}
|
||||
// What it encodes, it reads back the same.
|
||||
if raw, err := got.Encode(); err != nil {
|
||||
t.Errorf("Encode(%+v): %v", got, err)
|
||||
} else if again, err := DecodeRequest(raw); err != nil || !reflect.DeepEqual(again, got) {
|
||||
t.Errorf("Encode then DecodeRequest of %+v = %+v, %v", got, again, err)
|
||||
}
|
||||
}
|
||||
if (Request{Path: "api"}).Prefix() != "/api" || (Request{}).Prefix() != "" || (Request{Scheme: " HTTPS"}).SchemeOrDefault() != "https" ||
|
||||
(Request{}).SchemeOrDefault() != "http" || (Request{Redirect: "x"}).Forwards() {
|
||||
t.Error("Prefix, SchemeOrDefault or Forwards says otherwise than the contract")
|
||||
}
|
||||
|
||||
var schema struct {
|
||||
Properties map[string]json.RawMessage `json:"properties"`
|
||||
AdditionalProperties bool `json:"additionalProperties"`
|
||||
}
|
||||
if err := json.Unmarshal(Schema, &schema); err != nil || schema.AdditionalProperties {
|
||||
t.Fatalf("the schema is not a closed object: %v", err)
|
||||
}
|
||||
var tags []string
|
||||
for f := range reflect.TypeFor[Request]().Fields() {
|
||||
tags = append(tags, strings.Split(f.Tag.Get("json"), ",")[0])
|
||||
}
|
||||
var said []string
|
||||
for name := range schema.Properties {
|
||||
said = append(said, name)
|
||||
}
|
||||
slices.Sort(tags)
|
||||
slices.Sort(said)
|
||||
if !slices.Equal(tags, said) {
|
||||
t.Fatalf("the schema names %v, the decoder reads %v", said, tags)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user