1
0
mirror of https://github.com/IBM/fp-go.git synced 2025-11-25 22:21:49 +02:00
Files
fp-go/v2/http/headers/headers_test.go
Dr. Carsten Leue 4f8a557072 fix: simplify type hints
Signed-off-by: Dr. Carsten Leue <carsten.leue@de.ibm.com>
2025-11-11 15:24:45 +01:00

338 lines
9.7 KiB
Go

// 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"
F "github.com/IBM/fp-go/v2/function"
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(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))
}
// 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"))
}