Skip to content

Commit 60e1444

Browse files
committed
test: Improve test coverage across multiple packages
- Add comprehensive tests for api package (parseX5CFromArray, parseX5CFromJWK, ServerContext methods, legacy evaluation, TSLs handler) - Add tests for composite registry (options, resource types, resolution-only, refresh with errors) - Add tests for didweb registry (SetHTTPClient, invalid resource type, empty subject ID, mock server, resolution-only mode) - Add tests for oidfed registry (extractEntityID, cache eviction/invalidation/expiration/clear, cache stats, resolution-only, trust marks context) - Update MockRegistry to support configurable refreshErr - Update CONTRIBUTING.md Go version from 1.18 to 1.25
1 parent 8cba990 commit 60e1444

File tree

6 files changed

+960
-3
lines changed

6 files changed

+960
-3
lines changed

CONTRIBUTING.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ This project follows standard open source community guidelines. Please be respec
2020

2121
### Prerequisites
2222

23-
- Go 1.18 or later
23+
- Go 1.25 or later
2424
- Make
2525
- Git
2626
- (Optional) Docker for containerized testing

pkg/api/api_test.go

Lines changed: 292 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -558,3 +558,295 @@ func TestRateLimiting_Disabled(t *testing.T) {
558558
assert.Equal(t, 200, w.Code, "Request %d should succeed when rate limiting disabled", i+1)
559559
}
560560
}
561+
562+
// TestParseX5CFromArray tests the parseX5CFromArray function
563+
func TestParseX5CFromArray(t *testing.T) {
564+
tests := []struct {
565+
name string
566+
key []interface{}
567+
wantErr bool
568+
errMsg string
569+
}{
570+
{
571+
name: "empty key array",
572+
key: []interface{}{},
573+
wantErr: true,
574+
errMsg: "resource.key is empty",
575+
},
576+
{
577+
name: "nil key array",
578+
key: nil,
579+
wantErr: true,
580+
errMsg: "resource.key is empty",
581+
},
582+
{
583+
name: "non-string element",
584+
key: []interface{}{123},
585+
wantErr: true,
586+
errMsg: "resource.key[0] is not a string",
587+
},
588+
{
589+
name: "invalid base64",
590+
key: []interface{}{"not-valid-base64!!!"},
591+
wantErr: true,
592+
errMsg: "failed to base64 decode",
593+
},
594+
{
595+
name: "invalid certificate",
596+
key: []interface{}{base64.StdEncoding.EncodeToString([]byte("not a certificate"))},
597+
wantErr: true,
598+
errMsg: "failed to parse certificate",
599+
},
600+
{
601+
name: "valid certificate",
602+
key: []interface{}{testCertBase64},
603+
wantErr: false,
604+
},
605+
}
606+
607+
for _, tt := range tests {
608+
t.Run(tt.name, func(t *testing.T) {
609+
certs, err := parseX5CFromArray(tt.key)
610+
if tt.wantErr {
611+
assert.Error(t, err)
612+
if tt.errMsg != "" {
613+
assert.Contains(t, err.Error(), tt.errMsg)
614+
}
615+
} else {
616+
assert.NoError(t, err)
617+
assert.Len(t, certs, 1)
618+
assert.Equal(t, "Test Cert", certs[0].Subject.CommonName)
619+
}
620+
})
621+
}
622+
}
623+
624+
// TestParseX5CFromJWK tests the parseX5CFromJWK function
625+
func TestParseX5CFromJWK(t *testing.T) {
626+
tests := []struct {
627+
name string
628+
key []interface{}
629+
wantErr bool
630+
errMsg string
631+
}{
632+
{
633+
name: "empty key array",
634+
key: []interface{}{},
635+
wantErr: true,
636+
errMsg: "resource.key is empty",
637+
},
638+
{
639+
name: "nil key array",
640+
key: nil,
641+
wantErr: true,
642+
errMsg: "resource.key is empty",
643+
},
644+
{
645+
name: "non-map element",
646+
key: []interface{}{"not a map"},
647+
wantErr: true,
648+
errMsg: "resource.key[0] is not a JWK object (map)",
649+
},
650+
{
651+
name: "no x5c claim",
652+
key: []interface{}{
653+
map[string]interface{}{
654+
"kty": "RSA",
655+
"n": "somevalue",
656+
},
657+
},
658+
wantErr: true,
659+
errMsg: "JWK does not contain x5c claim",
660+
},
661+
{
662+
name: "x5c is not array",
663+
key: []interface{}{
664+
map[string]interface{}{
665+
"kty": "RSA",
666+
"x5c": "not an array",
667+
},
668+
},
669+
wantErr: true,
670+
errMsg: "JWK x5c claim is not an array",
671+
},
672+
{
673+
name: "x5c element is not string",
674+
key: []interface{}{
675+
map[string]interface{}{
676+
"kty": "RSA",
677+
"x5c": []interface{}{123},
678+
},
679+
},
680+
wantErr: true,
681+
errMsg: "JWK x5c[0] is not a string",
682+
},
683+
{
684+
name: "invalid base64 in x5c",
685+
key: []interface{}{
686+
map[string]interface{}{
687+
"kty": "RSA",
688+
"x5c": []interface{}{"not-valid-base64!!!"},
689+
},
690+
},
691+
wantErr: true,
692+
errMsg: "failed to base64 decode JWK x5c[0]",
693+
},
694+
{
695+
name: "invalid certificate in x5c",
696+
key: []interface{}{
697+
map[string]interface{}{
698+
"kty": "RSA",
699+
"x5c": []interface{}{base64.StdEncoding.EncodeToString([]byte("not a cert"))},
700+
},
701+
},
702+
wantErr: true,
703+
errMsg: "failed to parse certificate from JWK x5c[0]",
704+
},
705+
{
706+
name: "valid JWK with x5c",
707+
key: []interface{}{
708+
map[string]interface{}{
709+
"kty": "RSA",
710+
"x5c": []interface{}{testCertBase64},
711+
},
712+
},
713+
wantErr: false,
714+
},
715+
}
716+
717+
for _, tt := range tests {
718+
t.Run(tt.name, func(t *testing.T) {
719+
certs, err := parseX5CFromJWK(tt.key)
720+
if tt.wantErr {
721+
assert.Error(t, err)
722+
if tt.errMsg != "" {
723+
assert.Contains(t, err.Error(), tt.errMsg)
724+
}
725+
} else {
726+
assert.NoError(t, err)
727+
assert.Len(t, certs, 1)
728+
assert.Equal(t, "Test Cert", certs[0].Subject.CommonName)
729+
}
730+
})
731+
}
732+
}
733+
734+
// TestServerContext_WithLogger tests the WithLogger method
735+
func TestServerContext_WithLogger(t *testing.T) {
736+
logger := logging.NewLogger(logging.DebugLevel)
737+
serverCtx := NewServerContext(logger)
738+
739+
// Apply WithLogger method to create a new context with a different logger
740+
newLogger := logging.NewLogger(logging.InfoLevel)
741+
newCtx := serverCtx.WithLogger(newLogger)
742+
743+
assert.NotNil(t, newCtx.Logger)
744+
assert.NotSame(t, serverCtx, newCtx, "WithLogger should return a new ServerContext")
745+
}
746+
747+
// TestServerContext_WithLogger_NilLogger tests that WithLogger handles nil logger
748+
func TestServerContext_WithLogger_NilLogger(t *testing.T) {
749+
logger := logging.NewLogger(logging.DebugLevel)
750+
serverCtx := NewServerContext(logger)
751+
752+
// Pass nil logger - should use default
753+
newCtx := serverCtx.WithLogger(nil)
754+
755+
assert.NotNil(t, newCtx.Logger, "WithLogger(nil) should use default logger")
756+
}
757+
758+
// TestNewServerContext_DefaultValues tests NewServerContext default values
759+
func TestNewServerContext_DefaultValues(t *testing.T) {
760+
logger := logging.NewLogger(logging.InfoLevel)
761+
serverCtx := NewServerContext(logger)
762+
763+
assert.NotNil(t, serverCtx.Logger)
764+
assert.NotNil(t, serverCtx.LastProcessed)
765+
}
766+
767+
// TestLegacyEvaluate tests the legacy evaluation path (when RegistryManager is nil)
768+
func TestLegacyEvaluate(t *testing.T) {
769+
gin.SetMode(gin.TestMode)
770+
771+
logger := logging.NewLogger(logging.InfoLevel)
772+
serverCtx := NewServerContext(logger)
773+
// Note: RegistryManager is nil, so legacyEvaluate will be called
774+
serverCtx.RegistryManager = nil
775+
776+
router := gin.New()
777+
RegisterAPIRoutes(router, serverCtx)
778+
779+
// Test the evaluation endpoint without RegistryManager - should trigger legacy path
780+
body := `{
781+
"subject": {
782+
"type": "key",
783+
"id": "did:example:test"
784+
},
785+
"resource": {
786+
"type": "x5c",
787+
"id": "did:example:test",
788+
"key": ["dGVzdA=="]
789+
}
790+
}`
791+
792+
req := httptest.NewRequest("POST", "/evaluation", strings.NewReader(body))
793+
req.Header.Set("Content-Type", "application/json")
794+
w := httptest.NewRecorder()
795+
router.ServeHTTP(w, req)
796+
797+
// Legacy endpoint should return 200 with decision=false and error message
798+
assert.Equal(t, 200, w.Code)
799+
800+
var resp authzen.EvaluationResponse
801+
err := json.Unmarshal(w.Body.Bytes(), &resp)
802+
assert.NoError(t, err)
803+
assert.False(t, resp.Decision, "Legacy mode should return false decision")
804+
assert.NotNil(t, resp.Context)
805+
assert.NotNil(t, resp.Context.Reason)
806+
assert.Contains(t, resp.Context.Reason["error"], "legacy mode not supported")
807+
}
808+
809+
// TestTSLsHandler_EmptyRegistryManager tests TSLsHandler when no ETSI registries exist
810+
func TestTSLsHandler_EmptyRegistryManager(t *testing.T) {
811+
gin.SetMode(gin.TestMode)
812+
813+
logger := logging.NewLogger(logging.InfoLevel)
814+
serverCtx := NewServerContext(logger)
815+
816+
// Create registry manager with non-ETSI registry
817+
mgr := registry.NewRegistryManager(registry.FirstMatch, 10*time.Second)
818+
mockReg := &mockTrustRegistry{certPool: x509.NewCertPool()}
819+
mgr.Register(mockReg)
820+
serverCtx.RegistryManager = mgr
821+
822+
router := gin.New()
823+
RegisterAPIRoutes(router, serverCtx)
824+
825+
req := httptest.NewRequest("GET", "/tsls", nil)
826+
w := httptest.NewRecorder()
827+
router.ServeHTTP(w, req)
828+
829+
assert.Equal(t, 200, w.Code)
830+
// The response contains registry information
831+
assert.Contains(t, w.Body.String(), "registries")
832+
}
833+
834+
// TestTSLsHandler_NilRegistryManager tests TSLsHandler when RegistryManager is nil
835+
func TestTSLsHandler_NilRegistryManager(t *testing.T) {
836+
gin.SetMode(gin.TestMode)
837+
838+
logger := logging.NewLogger(logging.InfoLevel)
839+
serverCtx := NewServerContext(logger)
840+
serverCtx.RegistryManager = nil
841+
842+
router := gin.New()
843+
RegisterAPIRoutes(router, serverCtx)
844+
845+
req := httptest.NewRequest("GET", "/tsls", nil)
846+
w := httptest.NewRecorder()
847+
router.ServeHTTP(w, req)
848+
849+
// When RegistryManager is nil, it returns 200 with empty registries
850+
assert.Equal(t, 200, w.Code)
851+
assert.Contains(t, w.Body.String(), "count")
852+
}

0 commit comments

Comments
 (0)