2025-11-06 09:27:00 +01:00
|
|
|
// Copyright (c) 2023 - 2025 IBM Corp.
|
|
|
|
|
// All rights reserved.
|
|
|
|
|
//
|
|
|
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
|
|
|
// you may not use this file except in compliance with the License.
|
|
|
|
|
// You may obtain a copy of the License at
|
|
|
|
|
//
|
|
|
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
|
//
|
|
|
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
|
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
|
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
|
|
|
// See the License for the specific language governing permissions and
|
|
|
|
|
// limitations under the License.
|
|
|
|
|
|
|
|
|
|
package headers
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"net/http"
|
|
|
|
|
"testing"
|
|
|
|
|
|
|
|
|
|
A "github.com/IBM/fp-go/v2/array"
|
|
|
|
|
"github.com/IBM/fp-go/v2/eq"
|
2025-11-06 13:53:02 +01:00
|
|
|
F "github.com/IBM/fp-go/v2/function"
|
2025-11-06 09:27:00 +01:00
|
|
|
LT "github.com/IBM/fp-go/v2/optics/lens/testing"
|
|
|
|
|
O "github.com/IBM/fp-go/v2/option"
|
|
|
|
|
RG "github.com/IBM/fp-go/v2/record/generic"
|
|
|
|
|
S "github.com/IBM/fp-go/v2/string"
|
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
var (
|
|
|
|
|
sEq = eq.FromEquals(S.Eq)
|
|
|
|
|
valuesEq = RG.Eq[http.Header](A.Eq(sEq))
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
func TestLaws(t *testing.T) {
|
|
|
|
|
name := "Content-Type"
|
|
|
|
|
fieldLaws := LT.AssertLaws[http.Header, O.Option[string]](t, O.Eq(sEq), valuesEq)(AtValue(name))
|
|
|
|
|
|
|
|
|
|
n := O.None[string]()
|
|
|
|
|
s1 := O.Some("s1")
|
|
|
|
|
|
|
|
|
|
def := make(http.Header)
|
|
|
|
|
|
|
|
|
|
v1 := make(http.Header)
|
|
|
|
|
v1.Set(name, "v1")
|
|
|
|
|
|
|
|
|
|
v2 := make(http.Header)
|
|
|
|
|
v2.Set("Other-Header", "v2")
|
|
|
|
|
|
|
|
|
|
assert.True(t, fieldLaws(def, n))
|
|
|
|
|
assert.True(t, fieldLaws(v1, n))
|
|
|
|
|
assert.True(t, fieldLaws(v2, n))
|
|
|
|
|
|
|
|
|
|
assert.True(t, fieldLaws(def, s1))
|
|
|
|
|
assert.True(t, fieldLaws(v1, s1))
|
|
|
|
|
assert.True(t, fieldLaws(v2, s1))
|
|
|
|
|
}
|
2025-11-06 13:53:02 +01:00
|
|
|
|
|
|
|
|
// TestMonoidEmpty tests the Monoid empty (identity) element
|
|
|
|
|
func TestMonoidEmpty(t *testing.T) {
|
|
|
|
|
empty := Monoid.Empty()
|
|
|
|
|
assert.NotNil(t, empty)
|
|
|
|
|
assert.Equal(t, 0, len(empty))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// TestMonoidConcat tests concatenating two header maps
|
|
|
|
|
func TestMonoidConcat(t *testing.T) {
|
|
|
|
|
h1 := make(http.Header)
|
|
|
|
|
h1.Set("X-Custom-1", "value1")
|
|
|
|
|
h1.Set("Authorization", "Bearer token1")
|
|
|
|
|
|
|
|
|
|
h2 := make(http.Header)
|
|
|
|
|
h2.Set("X-Custom-2", "value2")
|
|
|
|
|
h2.Set("Content-Type", "application/json")
|
|
|
|
|
|
|
|
|
|
result := Monoid.Concat(h1, h2)
|
|
|
|
|
|
|
|
|
|
assert.Equal(t, "value1", result.Get("X-Custom-1"))
|
|
|
|
|
assert.Equal(t, "value2", result.Get("X-Custom-2"))
|
|
|
|
|
assert.Equal(t, "Bearer token1", result.Get("Authorization"))
|
|
|
|
|
assert.Equal(t, "application/json", result.Get("Content-Type"))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// TestMonoidConcatWithOverlap tests concatenating headers with overlapping keys
|
|
|
|
|
func TestMonoidConcatWithOverlap(t *testing.T) {
|
|
|
|
|
h1 := make(http.Header)
|
|
|
|
|
h1.Set("X-Custom", "value1")
|
|
|
|
|
|
|
|
|
|
h2 := make(http.Header)
|
|
|
|
|
h2.Add("X-Custom", "value2")
|
|
|
|
|
|
|
|
|
|
result := Monoid.Concat(h1, h2)
|
|
|
|
|
|
|
|
|
|
// Both values should be present
|
|
|
|
|
values := result.Values("X-Custom")
|
|
|
|
|
assert.Contains(t, values, "value1")
|
|
|
|
|
assert.Contains(t, values, "value2")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// TestMonoidIdentity tests that concatenating with empty is identity
|
|
|
|
|
func TestMonoidIdentity(t *testing.T) {
|
|
|
|
|
h := make(http.Header)
|
|
|
|
|
h.Set("X-Test", "value")
|
|
|
|
|
|
|
|
|
|
empty := Monoid.Empty()
|
|
|
|
|
|
|
|
|
|
// Left identity: empty + h = h
|
|
|
|
|
leftResult := Monoid.Concat(empty, h)
|
|
|
|
|
assert.Equal(t, "value", leftResult.Get("X-Test"))
|
|
|
|
|
|
|
|
|
|
// Right identity: h + empty = h
|
|
|
|
|
rightResult := Monoid.Concat(h, empty)
|
|
|
|
|
assert.Equal(t, "value", rightResult.Get("X-Test"))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// TestAtValuesGet tests getting header values using AtValues lens
|
|
|
|
|
func TestAtValuesGet(t *testing.T) {
|
|
|
|
|
headers := make(http.Header)
|
|
|
|
|
headers.Set("Content-Type", "application/json")
|
|
|
|
|
headers.Add("Accept", "application/json")
|
|
|
|
|
headers.Add("Accept", "text/html")
|
|
|
|
|
|
|
|
|
|
// Get Content-Type values
|
|
|
|
|
ctLens := AtValues("Content-Type")
|
|
|
|
|
ctValuesOpt := ctLens.Get(headers)
|
|
|
|
|
assert.True(t, O.IsSome(ctValuesOpt))
|
|
|
|
|
ctValues := O.GetOrElse(F.Constant([]string{}))(ctValuesOpt)
|
|
|
|
|
assert.Equal(t, []string{"application/json"}, ctValues)
|
|
|
|
|
|
|
|
|
|
// Get Accept values (multiple)
|
|
|
|
|
acceptLens := AtValues("Accept")
|
|
|
|
|
acceptValuesOpt := acceptLens.Get(headers)
|
|
|
|
|
assert.True(t, O.IsSome(acceptValuesOpt))
|
|
|
|
|
acceptValues := O.GetOrElse(F.Constant([]string{}))(acceptValuesOpt)
|
|
|
|
|
assert.Equal(t, 2, len(acceptValues))
|
|
|
|
|
assert.Contains(t, acceptValues, "application/json")
|
|
|
|
|
assert.Contains(t, acceptValues, "text/html")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// TestAtValuesSet tests setting header values using AtValues lens
|
|
|
|
|
func TestAtValuesSet(t *testing.T) {
|
|
|
|
|
headers := make(http.Header)
|
|
|
|
|
headers.Set("X-Old", "old-value")
|
|
|
|
|
|
|
|
|
|
lens := AtValues("Content-Type")
|
|
|
|
|
newHeaders := lens.Set(O.Some([]string{"application/json", "text/plain"}))(headers)
|
|
|
|
|
|
|
|
|
|
// New header should be set
|
|
|
|
|
values := newHeaders.Values("Content-Type")
|
|
|
|
|
assert.Equal(t, 2, len(values))
|
|
|
|
|
assert.Contains(t, values, "application/json")
|
|
|
|
|
assert.Contains(t, values, "text/plain")
|
|
|
|
|
|
|
|
|
|
// Old header should still exist
|
|
|
|
|
assert.Equal(t, "old-value", newHeaders.Get("X-Old"))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// TestAtValuesCanonical tests that header names are canonicalized
|
|
|
|
|
func TestAtValuesCanonical(t *testing.T) {
|
|
|
|
|
headers := make(http.Header)
|
|
|
|
|
headers.Set("content-type", "application/json")
|
|
|
|
|
|
|
|
|
|
// Access with different casing
|
|
|
|
|
lens := AtValues("Content-Type")
|
|
|
|
|
valuesOpt := lens.Get(headers)
|
|
|
|
|
|
|
|
|
|
assert.True(t, O.IsSome(valuesOpt))
|
|
|
|
|
values := O.GetOrElse(F.Constant([]string{}))(valuesOpt)
|
|
|
|
|
assert.Equal(t, []string{"application/json"}, values)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// TestAtValueGet tests getting first header value using AtValue lens
|
|
|
|
|
func TestAtValueGet(t *testing.T) {
|
|
|
|
|
headers := make(http.Header)
|
|
|
|
|
headers.Set("Authorization", "Bearer token123")
|
|
|
|
|
|
|
|
|
|
lens := AtValue("Authorization")
|
|
|
|
|
value := lens.Get(headers)
|
|
|
|
|
|
|
|
|
|
assert.True(t, O.IsSome(value))
|
|
|
|
|
token := O.GetOrElse(F.Constant(""))(value)
|
|
|
|
|
assert.Equal(t, "Bearer token123", token)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// TestAtValueGetNone tests getting non-existent header returns None
|
|
|
|
|
func TestAtValueGetNone(t *testing.T) {
|
|
|
|
|
headers := make(http.Header)
|
|
|
|
|
|
|
|
|
|
lens := AtValue("X-Non-Existent")
|
|
|
|
|
value := lens.Get(headers)
|
|
|
|
|
|
|
|
|
|
assert.True(t, O.IsNone(value))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// TestAtValueSet tests setting header value using AtValue lens
|
|
|
|
|
func TestAtValueSet(t *testing.T) {
|
|
|
|
|
headers := make(http.Header)
|
|
|
|
|
|
|
|
|
|
lens := AtValue("Content-Type")
|
|
|
|
|
newHeaders := lens.Set(O.Some("application/json"))(headers)
|
|
|
|
|
|
|
|
|
|
value := lens.Get(newHeaders)
|
|
|
|
|
assert.True(t, O.IsSome(value))
|
|
|
|
|
|
|
|
|
|
ct := O.GetOrElse(F.Constant(""))(value)
|
|
|
|
|
assert.Equal(t, "application/json", ct)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// TestAtValueSetNone tests removing header using AtValue lens
|
|
|
|
|
func TestAtValueSetNone(t *testing.T) {
|
|
|
|
|
headers := make(http.Header)
|
|
|
|
|
headers.Set("X-Custom", "value")
|
|
|
|
|
|
|
|
|
|
lens := AtValue("X-Custom")
|
|
|
|
|
newHeaders := lens.Set(O.None[string]())(headers)
|
|
|
|
|
|
|
|
|
|
value := lens.Get(newHeaders)
|
|
|
|
|
assert.True(t, O.IsNone(value))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// TestAtValueMultipleValues tests AtValue with multiple header values
|
|
|
|
|
func TestAtValueMultipleValues(t *testing.T) {
|
|
|
|
|
headers := make(http.Header)
|
|
|
|
|
headers.Add("Accept", "application/json")
|
|
|
|
|
headers.Add("Accept", "text/html")
|
|
|
|
|
|
|
|
|
|
lens := AtValue("Accept")
|
|
|
|
|
value := lens.Get(headers)
|
|
|
|
|
|
|
|
|
|
assert.True(t, O.IsSome(value))
|
|
|
|
|
// Should get the first value
|
|
|
|
|
first := O.GetOrElse(F.Constant(""))(value)
|
|
|
|
|
assert.Equal(t, "application/json", first)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// TestHeaderConstants tests that header constants are correct
|
|
|
|
|
func TestHeaderConstants(t *testing.T) {
|
|
|
|
|
assert.Equal(t, "Accept", Accept)
|
|
|
|
|
assert.Equal(t, "Authorization", Authorization)
|
|
|
|
|
assert.Equal(t, "Content-Type", ContentType)
|
|
|
|
|
assert.Equal(t, "Content-Length", ContentLength)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// TestHeaderConstantsUsage tests using header constants with http.Header
|
|
|
|
|
func TestHeaderConstantsUsage(t *testing.T) {
|
|
|
|
|
headers := make(http.Header)
|
|
|
|
|
|
|
|
|
|
headers.Set(Accept, "application/json")
|
|
|
|
|
headers.Set(Authorization, "Bearer token")
|
|
|
|
|
headers.Set(ContentType, "application/json")
|
|
|
|
|
headers.Set(ContentLength, "1234")
|
|
|
|
|
|
|
|
|
|
assert.Equal(t, "application/json", headers.Get(Accept))
|
|
|
|
|
assert.Equal(t, "Bearer token", headers.Get(Authorization))
|
|
|
|
|
assert.Equal(t, "application/json", headers.Get(ContentType))
|
|
|
|
|
assert.Equal(t, "1234", headers.Get(ContentLength))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// TestAtValueWithConstants tests using AtValue with header constants
|
|
|
|
|
func TestAtValueWithConstants(t *testing.T) {
|
|
|
|
|
headers := make(http.Header)
|
|
|
|
|
headers.Set(ContentType, "application/json")
|
|
|
|
|
|
|
|
|
|
lens := AtValue(ContentType)
|
|
|
|
|
value := lens.Get(headers)
|
|
|
|
|
|
|
|
|
|
assert.True(t, O.IsSome(value))
|
|
|
|
|
ct := O.GetOrElse(F.Constant(""))(value)
|
|
|
|
|
assert.Equal(t, "application/json", ct)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// TestMonoidAssociativity tests that Monoid concatenation is associative
|
|
|
|
|
func TestMonoidAssociativity(t *testing.T) {
|
|
|
|
|
h1 := make(http.Header)
|
|
|
|
|
h1.Set("X-1", "value1")
|
|
|
|
|
|
|
|
|
|
h2 := make(http.Header)
|
|
|
|
|
h2.Set("X-2", "value2")
|
|
|
|
|
|
|
|
|
|
h3 := make(http.Header)
|
|
|
|
|
h3.Set("X-3", "value3")
|
|
|
|
|
|
|
|
|
|
// (h1 + h2) + h3
|
|
|
|
|
left := Monoid.Concat(Monoid.Concat(h1, h2), h3)
|
|
|
|
|
|
|
|
|
|
// h1 + (h2 + h3)
|
|
|
|
|
right := Monoid.Concat(h1, Monoid.Concat(h2, h3))
|
|
|
|
|
|
|
|
|
|
// Both should have all three headers
|
|
|
|
|
assert.Equal(t, "value1", left.Get("X-1"))
|
|
|
|
|
assert.Equal(t, "value2", left.Get("X-2"))
|
|
|
|
|
assert.Equal(t, "value3", left.Get("X-3"))
|
|
|
|
|
|
|
|
|
|
assert.Equal(t, "value1", right.Get("X-1"))
|
|
|
|
|
assert.Equal(t, "value2", right.Get("X-2"))
|
|
|
|
|
assert.Equal(t, "value3", right.Get("X-3"))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// TestAtValuesEmptyHeader tests AtValues with empty headers
|
|
|
|
|
func TestAtValuesEmptyHeader(t *testing.T) {
|
|
|
|
|
headers := make(http.Header)
|
|
|
|
|
|
|
|
|
|
lens := AtValues("X-Non-Existent")
|
|
|
|
|
valuesOpt := lens.Get(headers)
|
|
|
|
|
|
|
|
|
|
assert.True(t, O.IsNone(valuesOpt))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// TestComplexHeaderOperations tests complex operations combining lenses and monoid
|
|
|
|
|
func TestComplexHeaderOperations(t *testing.T) {
|
|
|
|
|
// Create initial headers
|
|
|
|
|
h1 := make(http.Header)
|
|
|
|
|
h1.Set("X-Initial", "initial")
|
|
|
|
|
|
|
|
|
|
// Use lens to add Content-Type
|
|
|
|
|
ctLens := AtValue(ContentType)
|
|
|
|
|
h2 := ctLens.Set(O.Some("application/json"))(h1)
|
|
|
|
|
|
|
|
|
|
// Use lens to add Authorization
|
|
|
|
|
authLens := AtValue(Authorization)
|
|
|
|
|
h3 := authLens.Set(O.Some("Bearer token"))(h2)
|
|
|
|
|
|
|
|
|
|
// Create additional headers
|
|
|
|
|
h4 := make(http.Header)
|
|
|
|
|
h4.Set("X-Additional", "additional")
|
|
|
|
|
|
|
|
|
|
// Combine using Monoid
|
|
|
|
|
final := Monoid.Concat(h3, h4)
|
|
|
|
|
|
|
|
|
|
// Verify all headers are present
|
|
|
|
|
assert.Equal(t, "initial", final.Get("X-Initial"))
|
|
|
|
|
assert.Equal(t, "application/json", final.Get(ContentType))
|
|
|
|
|
assert.Equal(t, "Bearer token", final.Get(Authorization))
|
|
|
|
|
assert.Equal(t, "additional", final.Get("X-Additional"))
|
|
|
|
|
}
|