From 8d04e4e62c8aa0bdd6c4e3c572f3c9f46ee9e0ea Mon Sep 17 00:00:00 2001 From: Laurent Senta Date: Thu, 18 May 2023 16:11:27 +0200 Subject: [PATCH 1/2] feat: add t0112 and implement checks over multiple headers --- tests/t0112_gateway_cors_test.go | 52 +++++++++++++++++++++ tooling/check/check.go | 80 +++++++++++++++++++++++++++----- tooling/test/sugar.go | 27 ++++++----- tooling/test/test.go | 3 +- 4 files changed, 139 insertions(+), 23 deletions(-) create mode 100644 tests/t0112_gateway_cors_test.go diff --git a/tests/t0112_gateway_cors_test.go b/tests/t0112_gateway_cors_test.go new file mode 100644 index 000000000..5c8bad346 --- /dev/null +++ b/tests/t0112_gateway_cors_test.go @@ -0,0 +1,52 @@ +package tests + +import ( + "testing" + + "github.com/ipfs/gateway-conformance/tooling/test" + . "github.com/ipfs/gateway-conformance/tooling/test" +) + +func TestCors(t *testing.T) { + cidHello := "bafkqabtimvwgy3yk" // hello + + tests := SugarTests{ + { + Name: "GET Response from Gateway should contain CORS headers", + Request: Request(). + Path("ipfs/{{CID}}/", cidHello), + Response: Expect(). + Headers( + Header("Access-Control-Allow-Origin").Equals("*"), + Header("Access-Control-Allow-Methods").Equals("GET"), + Header("Access-Control-Allow-Headers").Has("Range"), + Header("Access-Control-Expose-Headers").Has( + "Content-Range", + "Content-Length", + "X-Ipfs-Path", + "X-Ipfs-Roots", + ), + ), + }, + { + Name: "OPTIONS to Gateway succeeds", + Request: Request(). + Method("OPTIONS"). + Path("ipfs/{{CID}}/", cidHello), + Response: Expect(). + Headers( + Header("Access-Control-Allow-Origin").Equals("*"), + Header("Access-Control-Allow-Methods").Equals("GET"), + Header("Access-Control-Allow-Headers").Has("Range"), + Header("Access-Control-Expose-Headers").Has( + "Content-Range", + "Content-Length", + "X-Ipfs-Path", + "X-Ipfs-Roots", + ), + ), + }, + } + + test.Run(t, tests) +} diff --git a/tooling/check/check.go b/tooling/check/check.go index ef6f84380..0d70aeb89 100644 --- a/tooling/check/check.go +++ b/tooling/check/check.go @@ -57,8 +57,8 @@ var _ Check[string] = CheckWithHint[string]{} type CheckIsEmpty struct { } -func (c CheckIsEmpty) Check(v string) CheckOutput { - if v == "" { +func (c CheckIsEmpty) Check(v []string) CheckOutput { + if len(v) == 0 { return CheckOutput{ Success: true, } @@ -66,18 +66,18 @@ func (c CheckIsEmpty) Check(v string) CheckOutput { return CheckOutput{ Success: false, - Reason: fmt.Sprintf("expected empty string, got '%s'", v), + Reason: fmt.Sprintf("expected empty array, got '%s'", v), } } -var _ Check[string] = CheckIsEmpty{} +var _ Check[[]string] = CheckIsEmpty{} func IsEmpty(hint ...string) interface{} { if len(hint) > 1 { panic("hint can only be one string") } if len(hint) == 1 { - return WithHint[string]( + return WithHint[[]string]( hint[0], CheckIsEmpty{}, ) @@ -169,6 +169,64 @@ func IsEqualWithHint(hint string, value string, rest ...any) CheckWithHint[strin return WithHint[string](hint, IsEqual(value, rest...)) } +type CheckUniqAnd struct { + check Check[string] +} + +var _ Check[[]string] = &CheckUniqAnd{} + +func IsUniqAnd(check Check[string]) Check[[]string] { + return &CheckUniqAnd{ + check: check, + } +} + +func (c *CheckUniqAnd) Check(v []string) CheckOutput { + if len(v) != 1 { + return CheckOutput{ + Success: false, + Reason: "expected one element", + } + } + + return c.check.Check(v[0]) +} + +type CheckHas struct { + values []string +} + +var _ Check[[]string] = &CheckHas{} + +func Has(values ...string) Check[[]string] { + return &CheckHas{ + values: values, + } +} + +func (c *CheckHas) Check(v []string) CheckOutput { + for _, value := range c.values { + found := false + for _, v := range v { + if v == value { + found = true + break + } + } + + if !found { + return CheckOutput{ + Success: false, + Reason: fmt.Sprintf("expected to find '%s' in '%s'", value, v), + } + } + } + + return CheckOutput{ + Success: true, + } +} + type CheckContains struct { Value string } @@ -250,17 +308,17 @@ func (c CheckFunc[T]) Check(v T) CheckOutput { var _ Check[string] = &CheckFunc[string]{} -type CheckNot struct { - check Check[string] +type CheckNot[T any] struct { + check Check[T] } -func Not(check Check[string]) Check[string] { - return CheckNot{ +func Not[T any](check Check[T]) Check[T] { + return CheckNot[T]{ check: check, } } -func (c CheckNot) Check(v string) CheckOutput { +func (c CheckNot[T]) Check(v T) CheckOutput { result := c.check.Check(v) if result.Success { @@ -275,7 +333,7 @@ func (c CheckNot) Check(v string) CheckOutput { } } -var _ Check[string] = CheckNot{} +var _ Check[string] = CheckNot[string]{} type CheckIsJSONEqual struct { Value interface{} diff --git a/tooling/test/sugar.go b/tooling/test/sugar.go index 703cdd764..b870f498e 100644 --- a/tooling/test/sugar.go +++ b/tooling/test/sugar.go @@ -160,11 +160,11 @@ func (e ExpectBuilder) BodyWithHint(hint string, body interface{}) ExpectBuilder } type HeaderBuilder struct { - Key_ string `json:"key,omitempty"` - Value_ string `json:"value,omitempty"` - Check_ check.Check[string] `json:"check,omitempty"` - Hint_ string `json:"hint,omitempty"` - Not_ bool `json:"not,omitempty"` + Key_ string `json:"key,omitempty"` + Value_ string `json:"value,omitempty"` + Check_ check.Check[[]string] `json:"check,omitempty"` + Hint_ string `json:"hint,omitempty"` + Not_ bool `json:"not,omitempty"` } func Header(key string, rest ...any) HeaderBuilder { @@ -172,7 +172,7 @@ func Header(key string, rest ...any) HeaderBuilder { // check if rest[0] is a string if value, ok := rest[0].(string); ok { value := tmpl.Fmt(value, rest[1:]...) - return HeaderBuilder{Key_: key, Value_: value, Check_: check.IsEqual(value)} + return HeaderBuilder{Key_: key, Value_: value, Check_: check.IsUniqAnd(check.IsEqual(value))} } else { panic("rest[0] must be a string") } @@ -182,12 +182,12 @@ func Header(key string, rest ...any) HeaderBuilder { } func (h HeaderBuilder) Contains(value string, rest ...any) HeaderBuilder { - h.Check_ = check.Contains(value, rest...) + h.Check_ = check.IsUniqAnd(check.Contains(value, rest...)) return h } func (h HeaderBuilder) Matches(value string, rest ...any) HeaderBuilder { - h.Check_ = check.Matches(value, rest...) + h.Check_ = check.IsUniqAnd(check.Matches(value, rest...)) return h } @@ -197,7 +197,12 @@ func (h HeaderBuilder) Hint(hint string) HeaderBuilder { } func (h HeaderBuilder) Equals(value string, args ...any) HeaderBuilder { - h.Check_ = check.IsEqual(value, args...) + h.Check_ = check.IsUniqAnd(check.IsEqual(value, args...)) + return h +} + +func (h HeaderBuilder) Has(values ...string) HeaderBuilder { + h.Check_ = check.Has(values...) return h } @@ -207,9 +212,9 @@ func (h HeaderBuilder) IsEmpty() HeaderBuilder { } func (h HeaderBuilder) Checks(f func(string) bool) HeaderBuilder { - h.Check_ = check.CheckFunc[string]{ + h.Check_ = check.IsUniqAnd(check.CheckFunc[string]{ Fn: f, - } + }) return h } diff --git a/tooling/test/test.go b/tooling/test/test.go index f3b1bfe54..a957bdc86 100644 --- a/tooling/test/test.go +++ b/tooling/test/test.go @@ -145,7 +145,8 @@ func Run(t *testing.T, tests SugarTests) { for _, header := range test.Response.Headers_ { t.Run(fmt.Sprintf("Header %s", header.Key_), func(t *testing.T) { - actual := res.Header.Get(header.Key_) + actual := res.Header.Values(header.Key_) + c := header.Check_ if header.Not_ { c = check.Not(c) From 50f20959747cd06bd574f472e68868a7284f25b7 Mon Sep 17 00:00:00 2001 From: Laurent Senta Date: Fri, 26 May 2023 12:40:51 +0200 Subject: [PATCH 2/2] fix: use Has and fix test Co-authored-by: Marcin Rataj --- tests/t0112_gateway_cors_test.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/t0112_gateway_cors_test.go b/tests/t0112_gateway_cors_test.go index 5c8bad346..27f58012f 100644 --- a/tests/t0112_gateway_cors_test.go +++ b/tests/t0112_gateway_cors_test.go @@ -12,13 +12,13 @@ func TestCors(t *testing.T) { tests := SugarTests{ { - Name: "GET Response from Gateway should contain CORS headers", + Name: "GET Responses from Gateway should include CORS headers allowing JS from other origins to read the data cross-origin.", Request: Request(). Path("ipfs/{{CID}}/", cidHello), Response: Expect(). Headers( Header("Access-Control-Allow-Origin").Equals("*"), - Header("Access-Control-Allow-Methods").Equals("GET"), + Header("Access-Control-Allow-Methods").Has("GET"), Header("Access-Control-Allow-Headers").Has("Range"), Header("Access-Control-Expose-Headers").Has( "Content-Range", @@ -36,7 +36,7 @@ func TestCors(t *testing.T) { Response: Expect(). Headers( Header("Access-Control-Allow-Origin").Equals("*"), - Header("Access-Control-Allow-Methods").Equals("GET"), + Header("Access-Control-Allow-Methods").Has("GET"), Header("Access-Control-Allow-Headers").Has("Range"), Header("Access-Control-Expose-Headers").Has( "Content-Range",