// SPDX-License-Identifier: Apache-2.0
// SPDX-FileCopyrightText: Copyright The AILERON Gateway Authors

package security

import (
	"bytes"
	"crypto/ecdsa"
	"crypto/ed25519"
	"crypto/rsa"
	"encoding/base64"
	"errors"
	"io"
	"net/http"
	"reflect"
	"regexp"
	"sync"
	"testing"

	v1 "github.com/aileron-gateway/aileron-gateway/apis/app/v1"
	"github.com/aileron-gateway/aileron-gateway/app"
	"github.com/aileron-gateway/aileron-gateway/core"
	"github.com/aileron-gateway/aileron-gateway/kernel/testutil"
	"github.com/golang-jwt/jwt/v5"
	"github.com/google/go-cmp/cmp"
	"github.com/google/go-cmp/cmp/cmpopts"
)

var (
	testDataDir = "../../test/ut/app/jwt/"

	// testCommonKey is the common key string.
	testCommonKey = "test common key"

	// testRSAPrivateKeyPem is a RSA private key generated by the command below.
	// `openssl genrsa -out private.pem 2048`
	testRSAPrivateKeyPem = `
-----BEGIN RSA PRIVATE KEY-----
MIIEowIBAAKCAQEAtdQLIISR7U32gjak48FChNZtP+fDBcs5Xv9+sDkzlMA38Dwi
HnxyD5xyHMhPoKGe6s+lHM161KSyYkf3qO+iZx3Zzr6u3KH/AgCHr0kn0I4pAGYe
Heg0nSJNpL2UeLqcFk4u0qmz386qREN7SFA1+tIxM+io1iGuoqwcI/emIrm4J/oC
DytfXtWfa7yfYdkevX8lCs/JlA6ubZGVYz7qIQcRuMeEuWyD4veKX8406MntRQqS
ekK9NoacdGVfbl7FgkIPR6P5NM0Z6fH+VXuHps9TLydpe9qVEH0J60YOy/w4YN3a
2C6RJfS52Z8hzbNsAP/sYJsPZJFL/j29JCNQ9wIDAQABAoIBAQCMxa0TsO+VrP2D
1LCWUEKiu6x7hIB91NgHD4HTB4ZCzQrB6Jx1w90+7t1+IStc7qQtoTNw+Dj3tscQ
xi0c0uKGORsvMHwK/zSCJUznTZkQo29lCqqYdvV9DljUiZ4QVlGQB1nC6XYEeWlj
Tmd9FfSkyuUjDibhGdStxthGlG5AhQADmbrwEOSaRWSiATbUzV7B/HDJH0+gFl/a
g0C0yPETeqwIGCB8yFjIP20L6/U6RMBZy2DAaTvGs6vu0NuKzB/miKdSfp/DtCos
ZeMyWX4BMEyv4+lVWjX7lpheE2bp/ZqKVX0nATW15P26BhHerSCUEv2r8FBQutsJ
ddfgjim5AoGBAOdzA3PITXk0Eaq3Wwzuh5IqB5I7TGCdf0Ibt48VOavHf0LWdeEL
dQrJulE5QvW9tXQYjYCOgV9Ht161/UIgiZcAF1TlkMfwe1LZRb7Io9eG1w5YlcCp
3LjFBgRpvV1CtYMlD03WHreOltn6o+W+JxzCMBwBhJMR8IItvpufIjCrAoGBAMkd
k4lZNTEUq+o2kbDvUvbM+pq6pRrK05O66sjQ4WgVWQyxFbY9/UHeStgrrh64URdO
CsINh44yn3UwyaF173wqFL2lkWR3WrQd4F1RJtGWeGIPcW2ZyvxcB/yrBg/26Scm
TZZTEH9JpLRCHXAn8+0dpTJNtiG98YgQ3JsbeVjlAoGAKjvzmlInyQE2ZBeXVtHE
DWj7/rQxaMP0r+SM52AxKOZ7k3+8/PbxQam0BRRU4rDehrPgRmA6vV9B19ePwlxb
nyFWIJjPhZyddG3Pa1QDidXCMoz5rrGQZGsXgQCmnHkK1EaT91kmO8qaPmadN9Qu
IPJSgxnHzmqMn8SnLpCoVYcCgYB4Bv/LZAM75JNpd+HhkCOaieEywRvSbqRxMYf4
C7nLDZqU2YcUudBRJGLUutTzHpu+5ePDxqFVO18xaMmdZC5mkxLH0G2CYT0v6/jA
mLMrOLtmAsN2XufnTuGxaTx4AKtLoy+A/NgGr6PttNuu2RlaNWwnJnwBjUP+KdoP
dAkrBQKBgHkCW3SiYYKNX/Nvk7+w9+a7ZuGFTCXHwjGbx+M8F5khNW/483svnPTd
NUn+MRW3TBmkGV4GL7aZj08SCT1WSHBSvq8EoOer7bST68knMJj3bg7LaI0hr6LG
OgdR7ALpiUP6dHVWxdnwcZQZO71Cxa1CXwXxneAq8Kpqw8SsYhZY
-----END RSA PRIVATE KEY-----
`

	// testRSAPublicKeyPem is a RSA public key generated by the command below.
	// `openssl rsa -pubout -in private.pem -out public.pem`
	testRSAPublicKeyPem = `
-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAtdQLIISR7U32gjak48FC
hNZtP+fDBcs5Xv9+sDkzlMA38DwiHnxyD5xyHMhPoKGe6s+lHM161KSyYkf3qO+i
Zx3Zzr6u3KH/AgCHr0kn0I4pAGYeHeg0nSJNpL2UeLqcFk4u0qmz386qREN7SFA1
+tIxM+io1iGuoqwcI/emIrm4J/oCDytfXtWfa7yfYdkevX8lCs/JlA6ubZGVYz7q
IQcRuMeEuWyD4veKX8406MntRQqSekK9NoacdGVfbl7FgkIPR6P5NM0Z6fH+VXuH
ps9TLydpe9qVEH0J60YOy/w4YN3a2C6RJfS52Z8hzbNsAP/sYJsPZJFL/j29JCNQ
9wIDAQAB
-----END PUBLIC KEY-----
`

	// testECPrivateKeyPem is a ECDSA private key generated by the command below.
	// `openssl ecparam -name secp521r1 -genkey -noout -out private.pem`
	testECPrivateKeyPem = `
-----BEGIN EC PRIVATE KEY-----
MIHcAgEBBEIBsR6VVNxJMf0rROqSjy8VjczqPFhE32sv3X26iN+n1yFoOHuvPF50
UnBb8O31rER0EsFIwaF5gu8Z0Q61vtM+0zSgBwYFK4EEACOhgYkDgYYABAAwa4Pw
lMPIqogfl9ebFhG5eTzM4QoW//lDEgOpFfzsogiiJJ5lxGcrA6Tz7taNZomcYBrN
anoYHyaVmH3MAUdKcQEOUcyL8bs1bNG8NGHcFNkbDPltv+z/EbgCcVBmT5rUBVbs
OiyXRzLBOb5dIguiBLvINDcqG6k4ZLNtPnO9dwnUWA==
-----END EC PRIVATE KEY-----
`

	// testECPublicKeyPem is a ECDSA public key generated by the command below.
	// `openssl ec -in private.pem -pubout -out public.pem`
	testECPublicKeyPem = `
-----BEGIN PUBLIC KEY-----
MIGbMBAGByqGSM49AgEGBSuBBAAjA4GGAAQAMGuD8JTDyKqIH5fXmxYRuXk8zOEK
Fv/5QxIDqRX87KIIoiSeZcRnKwOk8+7WjWaJnGAazWp6GB8mlZh9zAFHSnEBDlHM
i/G7NWzRvDRh3BTZGwz5bb/s/xG4AnFQZk+a1AVW7Dosl0cywTm+XSILogS7yDQ3
KhupOGSzbT5zvXcJ1Fg=
-----END PUBLIC KEY-----
`

	// testEDPrivateKeyPem is a Ed25519 private key generated by the command below.
	// `openssl genpkey -algorithm ed25519 -out private.pem`
	testEDPrivateKeyPem = `
-----BEGIN PRIVATE KEY-----
MC4CAQAwBQYDK2VwBCIEILP/HgmajXdivvsGhsyvvywkyvndzv+o8Jrz2drZ4T5D
-----END PRIVATE KEY-----
`

	// testEDPublicKeyPem is a Ed25519 public key generated by the command below.
	// `openssl pkey -in private.pem -pubout -out public.pem`
	testEDPublicKeyPem = `
-----BEGIN PUBLIC KEY-----
MCowBQYDK2VwAyEAikGOV2pjSTYZplqBd6Cymuu2scFK7ftVjlmklVq3U80=
-----END PUBLIC KEY-----
`

	// testJWKSet is a JWK set.
	// This set was created with https://mkjwk.org/
	// KeySize=2048, KeyUse=Signature, Algorithm=RS256, KeyID="tset"
	testJWKSet = `
{
	"keys": [
		{
			"p": "8Vbfaq3rEZ11NaJ-V8FRz0C1s6bjavXvib_UaM8sB9DGQgY5aDhFvIbcutD6fyUO8g0j0cXzNeLkfStcucp6twTR1ZnStOAV65-KLxzs1aNkiVEplRWTf0ZGAsSSVPBKTFTbBIC83CTxEWVBOPTosGZlYaiA1sjFMoc14roox48",
			"kty": "RSA",
			"q": "6dM9MWtwUtsTRxTS3pURxxtj1HZvkFEtkJtIWV3jualPwRI-5widpXT0sxGcnv0cslOKrtCEXKWW-0PzU3ckkjSimI7ltxCTyW6EUytRjp5fFsAQWsL6emaDWF7JuC3RPPQanRT0WCCoHXzL9ZiNRogOAdasmDL6POL02W7QAOc",
			"d": "bKXru7YZNp-1NJtt_t8LguGDkUgTevzUHTbQLR0a6NoUoCa3NKLcxytjqCdsXO_ZZrG1My7THyLVtB61zL9WrMCA7AUb6M4D3r7kSjUcIZ2AN4P-gbZVnAtn2xos0EuYU9qKweOb-TC66KztHOJ02sGoeojTqTrQ4XHwSDcGzMR3tTop_Qw88DhHuy3i2kbU2r1cYas9xXnmTYGgJGFf5Hv_LAiCMpy8AOyys-c2tqcJuXgJY21oozxXOnvsQST0_3IiG-M43vPvWp8mIMnVybBS4KzWQTllgXJ41QXAvLaFkbEF4gYExSM7ouVPFzBopjMP9ADXFF2RX66LPjUQmQ",
			"e": "AQAB",
			"use": "sig",
			"kid": "test",
			"qi": "D6iD83rzfVs4gC71xUw4jSgY9IDuM5q8dnEbEL7Hoa9TEHePyuUuTpGAuOiuqq_gj_3xUHAv7xdwCNR7fGvkl-hxZFkoAmNSWaJF8W8NfcdpGnhFS2GoUVpN8DzMVBRb5qEFygwvGFioJBTv1Zvh6si2SV7IPqb8BW-QylKXFo8",
			"dp": "HiygbnQkSeWH_Ba--TXghqsujLG3CW6KboyfgCM2_BssTqD8mH1AqtupyWnLiA4YGkduTRIU4I3nx4aeZ1Rbl3pwyeYbpCXIV9dwrYdOTT_KmCifrFYwRN-CbWIqcZhBkd2iLKPz-QpdYbFpQDfOyA9laZpp0XD0xuaE5D4mTx0",
			"alg": "RS256",
			"dq": "jS7J7RQVL0IuIqY1KMZThKhKH9gfRsFtRLot9h_Vx52ulUJCDtfJ8ERtF85ThkAIWrhMrX8j3JjzT-XYfxYaU9lKL9lgZKeyxFZhuaIXTYjcm2z-sv61pMBPIEWjgPzRMP-nLh-f5bJaL-vRbwpcizDNXFjVv9RP-gGDm-Hn4BM",
			"n": "3G81ooderiPSEWimMabYLuTFxZqwsWZ3PjRRP6ZJb0MLCKIc5_CKjHe5hoPhKG-jGuQeSAZDaVdr8uksvc1GK6VA-Zi_9rH5SRORTzcxnhbOsopr_I0gbtWJNIdMZxglJiSae0WE0SLK3V2UQ2R9Uq4IDbSBi72y156g9k9W399mrWphqw5HtLoRHoQn3gjKD3b2gUYbSROXSL27hNZZBYVsAnufyE5ASHHM_grk3WjP0-FrrUQrhsMqYMl4xzQHBTyV9akyBimXPqRldzVMTZr1JPUzdtmSWlERx0X4Pw4Ak67bEQ4gJqiMXqi4ybhYRg29vds1zf4nOdqukPwSCQ"
		}
	]
}
`
)

func TestSigningKeys(t *testing.T) {
	type condition struct {
		specs []*v1.SigningKeySpec
	}

	type action struct {
		keys       []*SigningKey
		err        any // error or errorutil.Kind
		errPattern *regexp.Regexp
	}

	cndNone := "None algorithm"
	cndRS := "RSxxx algorithm"
	cndPS := "PSxxx algorithm"
	cndES := "ESxxx algorithm"
	cndEdDSA := "EdDSA algorithm"
	cndHS := "HSxxx algorithm"
	cndValidKey := "valid key"
	cndEmptyKID := "empty KeyID"
	actCheckNoError := "check that there is no error"
	actCheckError := "check error"

	tb := testutil.NewTableBuilder[*condition, *action]()
	tb.Name(t.Name())
	tb.Condition(cndNone, "specify none algorithm")
	tb.Condition(cndRS, "specify one of RS253/RS384/RS512 algorithm")
	tb.Condition(cndPS, "specify one of PS253/PS384/PS512 algorithm")
	tb.Condition(cndES, "specify one of ES253/ES384/ES512 algorithm")
	tb.Condition(cndEdDSA, "specify EdDSA algorithm")
	tb.Condition(cndHS, "specify one of HS253/HS384/HS512 algorithm")
	tb.Condition(cndValidKey, "valid key")
	tb.Condition(cndEmptyKID, "specify no key id and hence generate it from signature")
	tb.Action(actCheckNoError, "check that there is no error returned")
	tb.Action(actCheckError, "check that an expected error was returned")
	table := tb.Build()

	gen := testutil.NewCase[*condition, *action]
	testCases := []*testutil.Case[*condition, *action]{
		gen(
			"None of KeyString",
			[]string{cndNone},
			[]string{actCheckNoError},
			&condition{
				specs: []*v1.SigningKeySpec{
					{
						KeyID:     "test",
						Algorithm: v1.SigningKeyAlgorithm_NONE,
					},
				},
			},
			&action{
				keys: []*SigningKey{
					{
						kid: "test",
						header: map[string]any{
							"alg": NONE,
							"kid": "test",
							"typ": "JWT",
						},
						method: jwt.SigningMethodNone,
					},
				},
			},
		),
		gen(
			"ES256 of KeyString",
			[]string{cndES, cndValidKey},
			[]string{actCheckNoError},
			&condition{
				specs: []*v1.SigningKeySpec{
					{
						KeyID:     "test1",
						Algorithm: v1.SigningKeyAlgorithm_ES256,
						KeyType:   v1.SigningKeyType_PUBLIC,
						KeyString: base64.StdEncoding.EncodeToString([]byte(testECPublicKeyPem)),
					},
					{
						KeyID:     "test2",
						Algorithm: v1.SigningKeyAlgorithm_ES256,
						KeyType:   v1.SigningKeyType_PRIVATE,
						KeyString: base64.StdEncoding.EncodeToString([]byte(testECPrivateKeyPem)),
					},
				},
			},
			&action{
				keys: []*SigningKey{
					{
						kid: "test1",
						header: map[string]any{
							"alg": ES256,
							"kid": "test1",
							"typ": "JWT",
						},
						method: jwt.SigningMethodES256,
					},
					{
						kid: "test2",
						header: map[string]any{
							"alg": ES256,
							"kid": "test2",
							"typ": "JWT",
						},
						method: jwt.SigningMethodES256,
					},
				},
			},
		),
		gen(
			"ES256 of KeyFilePath",
			[]string{cndES, cndValidKey},
			[]string{actCheckNoError},
			&condition{
				specs: []*v1.SigningKeySpec{
					{
						KeyID:       "test1",
						Algorithm:   v1.SigningKeyAlgorithm_ES256,
						KeyType:     v1.SigningKeyType_PUBLIC,
						KeyFilePath: testDataDir + "ecdsa_public_key.pem",
					},
					{
						KeyID:       "test2",
						Algorithm:   v1.SigningKeyAlgorithm_ES256,
						KeyType:     v1.SigningKeyType_PRIVATE,
						KeyFilePath: testDataDir + "ecdsa_private_key.pem",
					},
				},
			},
			&action{
				keys: []*SigningKey{
					{
						kid: "test1",
						header: map[string]any{
							"alg": ES256,
							"kid": "test1",
							"typ": "JWT",
						},
						method: jwt.SigningMethodES256,
					},
					{
						kid: "test2",
						header: map[string]any{
							"alg": ES256,
							"kid": "test2",
							"typ": "JWT",
						},
						method: jwt.SigningMethodES256,
					},
				},
			},
		),
		gen(
			"ES384 of KeyString",
			[]string{cndES, cndValidKey},
			[]string{actCheckNoError},
			&condition{
				specs: []*v1.SigningKeySpec{
					{
						KeyID:     "test1",
						Algorithm: v1.SigningKeyAlgorithm_ES384,
						KeyType:   v1.SigningKeyType_PUBLIC,
						KeyString: base64.StdEncoding.EncodeToString([]byte(testECPublicKeyPem)),
					},
					{
						KeyID:     "test2",
						Algorithm: v1.SigningKeyAlgorithm_ES384,
						KeyType:   v1.SigningKeyType_PRIVATE,
						KeyString: base64.StdEncoding.EncodeToString([]byte(testECPrivateKeyPem)),
					},
				},
			},
			&action{
				keys: []*SigningKey{
					{
						kid: "test1",
						header: map[string]any{
							"alg": ES384,
							"kid": "test1",
							"typ": "JWT",
						},
						method: jwt.SigningMethodES384,
					},
					{
						kid: "test2",
						header: map[string]any{
							"alg": ES384,
							"kid": "test2",
							"typ": "JWT",
						},
						method: jwt.SigningMethodES384,
					},
				},
			},
		),
		gen(
			"ES384 of KeyFilePath",
			[]string{cndES, cndValidKey},
			[]string{actCheckNoError},
			&condition{
				specs: []*v1.SigningKeySpec{
					{
						KeyID:       "test1",
						Algorithm:   v1.SigningKeyAlgorithm_ES384,
						KeyType:     v1.SigningKeyType_PUBLIC,
						KeyFilePath: testDataDir + "ecdsa_public_key.pem",
					},
					{
						KeyID:       "test2",
						Algorithm:   v1.SigningKeyAlgorithm_ES384,
						KeyType:     v1.SigningKeyType_PRIVATE,
						KeyFilePath: testDataDir + "ecdsa_private_key.pem",
					},
				},
			},
			&action{
				keys: []*SigningKey{
					{
						kid: "test1",
						header: map[string]any{
							"alg": ES384,
							"kid": "test1",
							"typ": "JWT",
						},
						method: jwt.SigningMethodES384,
					},
					{
						kid: "test2",
						header: map[string]any{
							"alg": ES384,
							"kid": "test2",
							"typ": "JWT",
						},
						method: jwt.SigningMethodES384,
					},
				},
			},
		),
		gen(
			"ES512 of KeyString",
			[]string{cndES, cndValidKey},
			[]string{actCheckNoError},
			&condition{
				specs: []*v1.SigningKeySpec{
					{
						KeyID:     "test1",
						Algorithm: v1.SigningKeyAlgorithm_ES512,
						KeyType:   v1.SigningKeyType_PUBLIC,
						KeyString: base64.StdEncoding.EncodeToString([]byte(testECPublicKeyPem)),
					},
					{
						KeyID:     "test2",
						Algorithm: v1.SigningKeyAlgorithm_ES512,
						KeyType:   v1.SigningKeyType_PRIVATE,
						KeyString: base64.StdEncoding.EncodeToString([]byte(testECPrivateKeyPem)),
					},
				},
			},
			&action{
				keys: []*SigningKey{
					{
						kid: "test1",
						header: map[string]any{
							"alg": ES512,
							"kid": "test1",
							"typ": "JWT",
						},
						method: jwt.SigningMethodES512,
					},
					{
						kid: "test2",
						header: map[string]any{
							"alg": ES512,
							"kid": "test2",
							"typ": "JWT",
						},
						method: jwt.SigningMethodES512,
					},
				},
			},
		),
		gen(
			"ES512 of KeyFilePath",
			[]string{cndES, cndValidKey},
			[]string{actCheckNoError},
			&condition{
				specs: []*v1.SigningKeySpec{
					{
						KeyID:       "test1",
						Algorithm:   v1.SigningKeyAlgorithm_ES512,
						KeyType:     v1.SigningKeyType_PUBLIC,
						KeyFilePath: testDataDir + "ecdsa_public_key.pem",
					},
					{
						KeyID:       "test2",
						Algorithm:   v1.SigningKeyAlgorithm_ES512,
						KeyType:     v1.SigningKeyType_PRIVATE,
						KeyFilePath: testDataDir + "ecdsa_private_key.pem",
					},
				},
			},
			&action{
				keys: []*SigningKey{
					{
						kid: "test1",
						header: map[string]any{
							"alg": ES512,
							"kid": "test1",
							"typ": "JWT",
						},
						method: jwt.SigningMethodES512,
					},
					{
						kid: "test2",
						header: map[string]any{
							"alg": ES512,
							"kid": "test2",
							"typ": "JWT",
						},
						method: jwt.SigningMethodES512,
					},
				},
			},
		),
		gen(
			"EdDSA of KeyString",
			[]string{cndEdDSA, cndValidKey},
			[]string{actCheckNoError},
			&condition{
				specs: []*v1.SigningKeySpec{
					{
						KeyID:     "test1",
						Algorithm: v1.SigningKeyAlgorithm_EdDSA,
						KeyType:   v1.SigningKeyType_PUBLIC,
						KeyString: base64.StdEncoding.EncodeToString([]byte(testEDPublicKeyPem)),
					},
					{
						KeyID:     "test2",
						Algorithm: v1.SigningKeyAlgorithm_EdDSA,
						KeyType:   v1.SigningKeyType_PRIVATE,
						KeyString: base64.StdEncoding.EncodeToString([]byte(testEDPrivateKeyPem)),
					},
				},
			},
			&action{
				keys: []*SigningKey{
					{
						kid: "test1",
						header: map[string]any{
							"alg": EdDSA,
							"kid": "test1",
							"typ": "JWT",
						},
						method: jwt.SigningMethodEdDSA,
					},
					{
						kid: "test2",
						header: map[string]any{
							"alg": EdDSA,
							"kid": "test2",
							"typ": "JWT",
						},
						method: jwt.SigningMethodEdDSA,
					},
				},
			},
		),
		gen(
			"EdDSA of KeyFilePath",
			[]string{cndEdDSA, cndValidKey},
			[]string{actCheckNoError},
			&condition{
				specs: []*v1.SigningKeySpec{
					{
						KeyID:       "test1",
						Algorithm:   v1.SigningKeyAlgorithm_EdDSA,
						KeyType:     v1.SigningKeyType_PUBLIC,
						KeyFilePath: testDataDir + "ed25519_public_key.pem",
					},
					{
						KeyID:       "test2",
						Algorithm:   v1.SigningKeyAlgorithm_EdDSA,
						KeyType:     v1.SigningKeyType_PRIVATE,
						KeyFilePath: testDataDir + "ed25519_private_key.pem",
					},
				},
			},
			&action{
				keys: []*SigningKey{
					{
						kid: "test1",
						header: map[string]any{
							"alg": EdDSA,
							"kid": "test1",
							"typ": "JWT",
						},
						method: jwt.SigningMethodEdDSA,
					},
					{
						kid: "test2",
						header: map[string]any{
							"alg": EdDSA,
							"kid": "test2",
							"typ": "JWT",
						},
						method: jwt.SigningMethodEdDSA,
					},
				},
			},
		),
		gen(
			"HS256 of KeyString",
			[]string{cndHS, cndValidKey},
			[]string{actCheckNoError},
			&condition{
				specs: []*v1.SigningKeySpec{
					{
						KeyID:     "test",
						Algorithm: v1.SigningKeyAlgorithm_HS256,
						KeyType:   v1.SigningKeyType_COMMON,
						KeyString: base64.StdEncoding.EncodeToString([]byte(testCommonKey)),
					},
				},
			},
			&action{
				keys: []*SigningKey{
					{
						kid: "test",
						header: map[string]any{
							"alg": HS256,
							"kid": "test",
							"typ": "JWT",
						},
						method: jwt.SigningMethodHS256,
					},
				},
			},
		),
		gen(
			"HS256 of KeyFilePath",
			[]string{cndHS, cndValidKey},
			[]string{actCheckNoError},
			&condition{
				specs: []*v1.SigningKeySpec{
					{
						KeyID:       "test",
						Algorithm:   v1.SigningKeyAlgorithm_HS256,
						KeyType:     v1.SigningKeyType_COMMON,
						KeyFilePath: testDataDir + "common_key.txt",
					},
				},
			},
			&action{
				keys: []*SigningKey{
					{
						kid: "test",
						header: map[string]any{
							"alg": HS256,
							"kid": "test",
							"typ": "JWT",
						},
						method: jwt.SigningMethodHS256,
					},
				},
			},
		),
		gen(
			"HS384 of KeyString",
			[]string{cndHS, cndValidKey},
			[]string{actCheckNoError},
			&condition{
				specs: []*v1.SigningKeySpec{
					{
						KeyID:     "test",
						Algorithm: v1.SigningKeyAlgorithm_HS384,
						KeyType:   v1.SigningKeyType_COMMON,
						KeyString: base64.StdEncoding.EncodeToString([]byte(testCommonKey)),
					},
				},
			},
			&action{
				keys: []*SigningKey{
					{
						kid: "test",
						header: map[string]any{
							"alg": HS384,
							"kid": "test",
							"typ": "JWT",
						},
						method: jwt.SigningMethodHS384,
					},
				},
			},
		),
		gen(
			"HS384 of KeyFilePath",
			[]string{cndHS, cndValidKey},
			[]string{actCheckNoError},
			&condition{
				specs: []*v1.SigningKeySpec{
					{
						KeyID:       "test",
						Algorithm:   v1.SigningKeyAlgorithm_HS384,
						KeyType:     v1.SigningKeyType_COMMON,
						KeyFilePath: testDataDir + "common_key.txt",
					},
				},
			},
			&action{
				keys: []*SigningKey{
					{
						kid: "test",
						header: map[string]any{
							"alg": HS384,
							"kid": "test",
							"typ": "JWT",
						},
						method: jwt.SigningMethodHS384,
					},
				},
			},
		),
		gen(
			"HS512 of KeyString",
			[]string{cndHS, cndValidKey},
			[]string{actCheckNoError},
			&condition{
				specs: []*v1.SigningKeySpec{
					{
						KeyID:     "test",
						Algorithm: v1.SigningKeyAlgorithm_HS512,
						KeyType:   v1.SigningKeyType_COMMON,
						KeyString: base64.StdEncoding.EncodeToString([]byte(testCommonKey)),
					},
				},
			},
			&action{
				keys: []*SigningKey{
					{
						kid: "test",
						header: map[string]any{
							"alg": HS512,
							"kid": "test",
							"typ": "JWT",
						},
						method: jwt.SigningMethodHS512,
					},
				},
			},
		),
		gen(
			"HS512 of KeyFilePath",
			[]string{cndHS, cndValidKey},
			[]string{actCheckNoError},
			&condition{
				specs: []*v1.SigningKeySpec{
					{
						KeyID:       "test",
						Algorithm:   v1.SigningKeyAlgorithm_HS512,
						KeyType:     v1.SigningKeyType_COMMON,
						KeyFilePath: testDataDir + "common_key.txt",
					},
				},
			},
			&action{
				keys: []*SigningKey{
					{
						kid: "test",
						header: map[string]any{
							"alg": HS512,
							"kid": "test",
							"typ": "JWT",
						},
						method: jwt.SigningMethodHS512,
					},
				},
			},
		),
		gen(
			"RS256 of KeyString",
			[]string{cndRS, cndValidKey},
			[]string{actCheckNoError},
			&condition{
				specs: []*v1.SigningKeySpec{
					{
						KeyID:     "test1",
						Algorithm: v1.SigningKeyAlgorithm_RS256,
						KeyType:   v1.SigningKeyType_PUBLIC,
						KeyString: base64.StdEncoding.EncodeToString([]byte(testRSAPublicKeyPem)),
					},
					{
						KeyID:     "test2",
						Algorithm: v1.SigningKeyAlgorithm_RS256,
						KeyType:   v1.SigningKeyType_PRIVATE,
						KeyString: base64.StdEncoding.EncodeToString([]byte(testRSAPrivateKeyPem)),
					},
				},
			},
			&action{
				keys: []*SigningKey{
					{
						kid: "test1",
						header: map[string]any{
							"alg": RS256,
							"kid": "test1",
							"typ": "JWT",
						},
						method: jwt.SigningMethodRS256,
					},
					{
						kid: "test2",
						header: map[string]any{
							"alg": RS256,
							"kid": "test2",
							"typ": "JWT",
						},
						method: jwt.SigningMethodRS256,
					},
				},
			},
		),
		gen(
			"RS256 of KeyFilePath",
			[]string{cndRS, cndValidKey},
			[]string{actCheckNoError},
			&condition{
				specs: []*v1.SigningKeySpec{
					{
						KeyID:       "test1",
						Algorithm:   v1.SigningKeyAlgorithm_RS256,
						KeyType:     v1.SigningKeyType_PUBLIC,
						KeyFilePath: testDataDir + "rsa_public_key.pem",
					},
					{
						KeyID:       "test2",
						Algorithm:   v1.SigningKeyAlgorithm_RS256,
						KeyType:     v1.SigningKeyType_PRIVATE,
						KeyFilePath: testDataDir + "rsa_private_key.pem",
					},
				},
			},
			&action{
				keys: []*SigningKey{
					{
						kid: "test1",
						header: map[string]any{
							"alg": RS256,
							"kid": "test1",
							"typ": "JWT",
						},
						method: jwt.SigningMethodRS256,
					},
					{
						kid: "test2",
						header: map[string]any{
							"alg": RS256,
							"kid": "test2",
							"typ": "JWT",
						},
						method: jwt.SigningMethodRS256,
					},
				},
			},
		),
		gen(
			"RS384 of KeyString",
			[]string{cndRS, cndValidKey},
			[]string{actCheckNoError},
			&condition{
				specs: []*v1.SigningKeySpec{
					{
						KeyID:     "test1",
						Algorithm: v1.SigningKeyAlgorithm_RS384,
						KeyType:   v1.SigningKeyType_PUBLIC,
						KeyString: base64.StdEncoding.EncodeToString([]byte(testRSAPublicKeyPem)),
					},
					{
						KeyID:     "test2",
						Algorithm: v1.SigningKeyAlgorithm_RS384,
						KeyType:   v1.SigningKeyType_PRIVATE,
						KeyString: base64.StdEncoding.EncodeToString([]byte(testRSAPrivateKeyPem)),
					},
				},
			},
			&action{
				keys: []*SigningKey{
					{
						kid: "test1",
						header: map[string]any{
							"alg": RS384,
							"kid": "test1",
							"typ": "JWT",
						},
						method: jwt.SigningMethodRS384,
					},
					{
						kid: "test2",
						header: map[string]any{
							"alg": RS384,
							"kid": "test2",
							"typ": "JWT",
						},
						method: jwt.SigningMethodRS384,
					},
				},
			},
		),
		gen(
			"RS384 of KeyFilePath",
			[]string{cndRS, cndValidKey},
			[]string{actCheckNoError},
			&condition{
				specs: []*v1.SigningKeySpec{
					{
						KeyID:       "test1",
						Algorithm:   v1.SigningKeyAlgorithm_RS384,
						KeyType:     v1.SigningKeyType_PUBLIC,
						KeyFilePath: testDataDir + "rsa_public_key.pem",
					},
					{
						KeyID:       "test2",
						Algorithm:   v1.SigningKeyAlgorithm_RS384,
						KeyType:     v1.SigningKeyType_PRIVATE,
						KeyFilePath: testDataDir + "rsa_private_key.pem",
					},
				},
			},
			&action{
				keys: []*SigningKey{
					{
						kid: "test1",
						header: map[string]any{
							"alg": RS384,
							"kid": "test1",
							"typ": "JWT",
						},
						method: jwt.SigningMethodRS384,
					},
					{
						kid: "test2",
						header: map[string]any{
							"alg": RS384,
							"kid": "test2",
							"typ": "JWT",
						},
						method: jwt.SigningMethodRS384,
					},
				},
			},
		),
		gen(
			"RS512 of KeyString",
			[]string{cndRS, cndValidKey},
			[]string{actCheckNoError},
			&condition{
				specs: []*v1.SigningKeySpec{
					{
						KeyID:     "test1",
						Algorithm: v1.SigningKeyAlgorithm_RS512,
						KeyType:   v1.SigningKeyType_PUBLIC,
						KeyString: base64.StdEncoding.EncodeToString([]byte(testRSAPublicKeyPem)),
					},
					{
						KeyID:     "test2",
						Algorithm: v1.SigningKeyAlgorithm_RS512,
						KeyType:   v1.SigningKeyType_PRIVATE,
						KeyString: base64.StdEncoding.EncodeToString([]byte(testRSAPrivateKeyPem)),
					},
				},
			},
			&action{
				keys: []*SigningKey{
					{
						kid: "test1",
						header: map[string]any{
							"alg": RS512,
							"kid": "test1",
							"typ": "JWT",
						},
						method: jwt.SigningMethodRS512,
					},
					{
						kid: "test2",
						header: map[string]any{
							"alg": RS512,
							"kid": "test2",
							"typ": "JWT",
						},
						method: jwt.SigningMethodRS512,
					},
				},
			},
		),
		gen(
			"RS512 of KeyFilePath",
			[]string{cndRS, cndValidKey},
			[]string{actCheckNoError},
			&condition{
				specs: []*v1.SigningKeySpec{
					{
						KeyID:       "test1",
						Algorithm:   v1.SigningKeyAlgorithm_RS512,
						KeyType:     v1.SigningKeyType_PUBLIC,
						KeyFilePath: testDataDir + "rsa_public_key.pem",
					},
					{
						KeyID:       "test2",
						Algorithm:   v1.SigningKeyAlgorithm_RS512,
						KeyType:     v1.SigningKeyType_PRIVATE,
						KeyFilePath: testDataDir + "rsa_private_key.pem",
					},
				},
			},
			&action{
				keys: []*SigningKey{
					{
						kid: "test1",
						header: map[string]any{
							"alg": RS512,
							"kid": "test1",
							"typ": "JWT",
						},
						method: jwt.SigningMethodRS512,
					},
					{
						kid: "test2",
						header: map[string]any{
							"alg": RS512,
							"kid": "test2",
							"typ": "JWT",
						},
						method: jwt.SigningMethodRS512,
					},
				},
			},
		),
		gen(
			"PS256 of KeyString",
			[]string{cndPS, cndValidKey},
			[]string{actCheckNoError},
			&condition{
				specs: []*v1.SigningKeySpec{
					{
						KeyID:     "test1",
						Algorithm: v1.SigningKeyAlgorithm_PS256,
						KeyType:   v1.SigningKeyType_PUBLIC,
						KeyString: base64.StdEncoding.EncodeToString([]byte(testRSAPublicKeyPem)),
					},
					{
						KeyID:     "test2",
						Algorithm: v1.SigningKeyAlgorithm_PS256,
						KeyType:   v1.SigningKeyType_PRIVATE,
						KeyString: base64.StdEncoding.EncodeToString([]byte(testRSAPrivateKeyPem)),
					},
				},
			},
			&action{
				keys: []*SigningKey{
					{
						kid: "test1",
						header: map[string]any{
							"alg": PS256,
							"kid": "test1",
							"typ": "JWT",
						},
						method: jwt.SigningMethodPS256,
					},
					{
						kid: "test2",
						header: map[string]any{
							"alg": PS256,
							"kid": "test2",
							"typ": "JWT",
						},
						method: jwt.SigningMethodPS256,
					},
				},
			},
		),
		gen(
			"PS256 of KeyFilePath",
			[]string{cndPS, cndValidKey},
			[]string{actCheckNoError},
			&condition{
				specs: []*v1.SigningKeySpec{
					{
						KeyID:       "test1",
						Algorithm:   v1.SigningKeyAlgorithm_PS256,
						KeyType:     v1.SigningKeyType_PUBLIC,
						KeyFilePath: testDataDir + "rsa_public_key.pem",
					},
					{
						KeyID:       "test2",
						Algorithm:   v1.SigningKeyAlgorithm_PS256,
						KeyType:     v1.SigningKeyType_PRIVATE,
						KeyFilePath: testDataDir + "rsa_private_key.pem",
					},
				},
			},
			&action{
				keys: []*SigningKey{
					{
						kid: "test1",
						header: map[string]any{
							"alg": PS256,
							"kid": "test1",
							"typ": "JWT",
						},
						method: jwt.SigningMethodPS256,
					},
					{
						kid: "test2",
						header: map[string]any{
							"alg": PS256,
							"kid": "test2",
							"typ": "JWT",
						},
						method: jwt.SigningMethodPS256,
					},
				},
			},
		),
		gen(
			"PS384 of KeyString",
			[]string{cndPS, cndValidKey},
			[]string{actCheckNoError},
			&condition{
				specs: []*v1.SigningKeySpec{
					{
						KeyID:     "test1",
						Algorithm: v1.SigningKeyAlgorithm_PS384,
						KeyType:   v1.SigningKeyType_PUBLIC,
						KeyString: base64.StdEncoding.EncodeToString([]byte(testRSAPublicKeyPem)),
					},
					{
						KeyID:     "test2",
						Algorithm: v1.SigningKeyAlgorithm_PS384,
						KeyType:   v1.SigningKeyType_PRIVATE,
						KeyString: base64.StdEncoding.EncodeToString([]byte(testRSAPrivateKeyPem)),
					},
				},
			},
			&action{
				keys: []*SigningKey{
					{
						kid: "test1",
						header: map[string]any{
							"alg": PS384,
							"kid": "test1",
							"typ": "JWT",
						},
						method: jwt.SigningMethodPS384,
					},
					{
						kid: "test2",
						header: map[string]any{
							"alg": PS384,
							"kid": "test2",
							"typ": "JWT",
						},
						method: jwt.SigningMethodPS384,
					},
				},
			},
		),
		gen(
			"PS384 of KeyFilePath",
			[]string{cndPS, cndValidKey},
			[]string{actCheckNoError},
			&condition{
				specs: []*v1.SigningKeySpec{
					{
						KeyID:       "test1",
						Algorithm:   v1.SigningKeyAlgorithm_PS384,
						KeyType:     v1.SigningKeyType_PUBLIC,
						KeyFilePath: testDataDir + "rsa_public_key.pem",
					},
					{
						KeyID:       "test2",
						Algorithm:   v1.SigningKeyAlgorithm_PS384,
						KeyType:     v1.SigningKeyType_PRIVATE,
						KeyFilePath: testDataDir + "rsa_private_key.pem",
					},
				},
			},
			&action{
				keys: []*SigningKey{
					{
						kid: "test1",
						header: map[string]any{
							"alg": PS384,
							"kid": "test1",
							"typ": "JWT",
						},
						method: jwt.SigningMethodPS384,
					},
					{
						kid: "test2",
						header: map[string]any{
							"alg": PS384,
							"kid": "test2",
							"typ": "JWT",
						},
						method: jwt.SigningMethodPS384,
					},
				},
			},
		),
		gen(
			"PS512 of KeyString",
			[]string{cndPS, cndValidKey},
			[]string{actCheckNoError},
			&condition{
				specs: []*v1.SigningKeySpec{
					{
						KeyID:     "test1",
						Algorithm: v1.SigningKeyAlgorithm_PS512,
						KeyType:   v1.SigningKeyType_PUBLIC,
						KeyString: base64.StdEncoding.EncodeToString([]byte(testRSAPublicKeyPem)),
					},
					{
						KeyID:     "test2",
						Algorithm: v1.SigningKeyAlgorithm_PS512,
						KeyType:   v1.SigningKeyType_PRIVATE,
						KeyString: base64.StdEncoding.EncodeToString([]byte(testRSAPrivateKeyPem)),
					},
				},
			},
			&action{
				keys: []*SigningKey{
					{
						kid: "test1",
						header: map[string]any{
							"alg": PS512,
							"kid": "test1",
							"typ": "JWT",
						},
						method: jwt.SigningMethodPS512,
					},
					{
						kid: "test2",
						header: map[string]any{
							"alg": PS512,
							"kid": "test2",
							"typ": "JWT",
						},
						method: jwt.SigningMethodPS512,
					},
				},
			},
		),
		gen(
			"PS512 of KeyFilePath",
			[]string{cndPS, cndValidKey},
			[]string{actCheckNoError},
			&condition{
				specs: []*v1.SigningKeySpec{
					{
						KeyID:       "test1",
						Algorithm:   v1.SigningKeyAlgorithm_PS512,
						KeyType:     v1.SigningKeyType_PUBLIC,
						KeyFilePath: testDataDir + "rsa_public_key.pem",
					},
					{
						KeyID:       "test2",
						Algorithm:   v1.SigningKeyAlgorithm_PS512,
						KeyType:     v1.SigningKeyType_PRIVATE,
						KeyFilePath: testDataDir + "rsa_private_key.pem",
					},
				},
			},
			&action{
				keys: []*SigningKey{
					{
						kid: "test1",
						header: map[string]any{
							"alg": PS512,
							"kid": "test1",
							"typ": "JWT",
						},
						method: jwt.SigningMethodPS512,
					},
					{
						kid: "test2",
						header: map[string]any{
							"alg": PS512,
							"kid": "test2",
							"typ": "JWT",
						},
						method: jwt.SigningMethodPS512,
					},
				},
			},
		),
		gen(
			"generate kid",
			[]string{cndEmptyKID, cndHS, cndValidKey},
			[]string{actCheckNoError},
			&condition{
				specs: []*v1.SigningKeySpec{
					{
						KeyID:     "", // kid will be generated.
						Algorithm: v1.SigningKeyAlgorithm_HS256,
						KeyType:   v1.SigningKeyType_COMMON,
						KeyString: base64.StdEncoding.EncodeToString([]byte(testCommonKey)),
					},
				},
			},
			&action{
				keys: []*SigningKey{
					{
						kid: "JHHVXBGXCP76RVRJWF4YZYFL6KQSBHHM",
						header: map[string]any{
							"alg": HS256,
							"kid": "JHHVXBGXCP76RVRJWF4YZYFL6KQSBHHM",
							"typ": "JWT",
						},
						method: jwt.SigningMethodHS256,
					},
				},
			},
		),
		gen(
			"with user defined header",
			[]string{cndHS, cndValidKey},
			[]string{actCheckNoError},
			&condition{
				specs: []*v1.SigningKeySpec{
					{
						KeyID:     "test",
						Algorithm: v1.SigningKeyAlgorithm_HS256,
						KeyType:   v1.SigningKeyType_COMMON,
						KeyString: base64.StdEncoding.EncodeToString([]byte(testCommonKey)),
						JWTHeader: map[string]string{"foo": "bar"},
					},
				},
			},
			&action{
				keys: []*SigningKey{
					{
						kid: "test",
						header: map[string]any{
							"alg": HS256,
							"kid": "test",
							"typ": "JWT",
							"foo": "bar",
						},
						method: jwt.SigningMethodHS256,
					},
				},
			},
		),
		gen(
			"invalid algorithm",
			[]string{},
			[]string{actCheckError},
			&condition{
				specs: []*v1.SigningKeySpec{
					{
						KeyID:       "test",
						Algorithm:   v1.SigningKeyAlgorithm_ALGORITHM_UNKNOWN,
						KeyType:     v1.SigningKeyType_PUBLIC,
						KeyFilePath: testDataDir + "rsa_public_key.pem",
					},
				},
			},
			&action{
				keys:       nil,
				err:        app.ErrAppUtilGenerateJWTKey,
				errPattern: regexp.MustCompile(core.ErrPrefix + `failed to generate JWT key`),
			},
		),
		gen(
			"invalid key string",
			[]string{},
			[]string{actCheckError},
			&condition{
				specs: []*v1.SigningKeySpec{
					{
						KeyID:     "test",
						Algorithm: v1.SigningKeyAlgorithm_HS256,
						KeyType:   v1.SigningKeyType_COMMON,
						KeyString: "invalid-key-string",
					},
				},
			},
			&action{
				keys:       nil,
				err:        app.ErrAppUtilGenerateJWTKey,
				errPattern: regexp.MustCompile(core.ErrPrefix + `failed to generate JWT key`),
			},
		),
		gen(
			"invalid key file path",
			[]string{},
			[]string{actCheckError},
			&condition{
				specs: []*v1.SigningKeySpec{
					{
						KeyID:       "test",
						Algorithm:   v1.SigningKeyAlgorithm_HS256,
						KeyType:     v1.SigningKeyType_COMMON,
						KeyFilePath: testDataDir + "invalid-file-path.txt",
					},
				},
			},
			&action{
				keys:       nil,
				err:        app.ErrAppUtilGenerateJWTKey,
				errPattern: regexp.MustCompile(core.ErrPrefix + `failed to generate JWT key`),
			},
		),
		gen(
			"key parse error",
			[]string{},
			[]string{actCheckError},
			&condition{
				specs: []*v1.SigningKeySpec{
					{
						KeyID:     "test",
						Algorithm: v1.SigningKeyAlgorithm_RS256,
						KeyType:   v1.SigningKeyType_PRIVATE,
						KeyString: base64.StdEncoding.EncodeToString([]byte("invalid-key-string")),
					},
				},
			},
			&action{
				keys:       nil,
				err:        app.ErrAppUtilGenerateJWTKey,
				errPattern: regexp.MustCompile(core.ErrPrefix + `failed to generate JWT key`),
			},
		),
	}

	testutil.Register(table, testCases...)

	for _, tt := range table.Entries() {
		tt := tt

		t.Run(tt.Name(), func(t *testing.T) {
			keys, err := SigningKeys(true, tt.C().specs...)
			testutil.DiffError(t, tt.A().err, tt.A().errPattern, err)

			opts := []cmp.Option{
				cmp.AllowUnexported(SigningKey{}),
				cmpopts.IgnoreFields(SigningKey{}, "key"),
			}
			testutil.Diff(t, tt.A().keys, keys, opts...)
		})
	}
}

func TestParseKey(t *testing.T) {
	type condition struct {
		typ v1.SigningKeyType
		alg Algorithm
		b   []byte
	}

	type action struct {
		key any
		err any // error or errorutil.Kind
	}

	cndPrivate := "private key"
	cndPublic := "public key"
	cndCommon := "common key"
	cndInvalidType := "invalid key type"
	cndEmptyKey := "empty key"
	actCheckNoError := "check that there is no error"
	actCheckError := "check error"

	tb := testutil.NewTableBuilder[*condition, *action]()
	tb.Name(t.Name())
	tb.Condition(cndPrivate, "specify private key type")
	tb.Condition(cndPublic, "specify public key type")
	tb.Condition(cndCommon, "specify common key type")
	tb.Condition(cndInvalidType, "specify invalid key type")
	tb.Condition(cndEmptyKey, "key required but empty")
	tb.Action(actCheckNoError, "check that there is no error returned")
	tb.Action(actCheckError, "check that an expected error was returned")
	table := tb.Build()

	gen := testutil.NewCase[*condition, *action]
	testCases := []*testutil.Case[*condition, *action]{
		gen(
			"RS256 private key",
			[]string{cndPrivate},
			[]string{actCheckNoError},
			&condition{
				typ: v1.SigningKeyType_PRIVATE,
				alg: RS256,
				b:   []byte(testRSAPrivateKeyPem),
			},
			&action{
				key: &rsa.PrivateKey{},
			},
		),
		gen(
			"RS256 public key",
			[]string{cndPublic},
			[]string{actCheckNoError},
			&condition{
				typ: v1.SigningKeyType_PUBLIC,
				alg: RS256,
				b:   []byte(testRSAPublicKeyPem),
			},
			&action{
				key: &rsa.PublicKey{},
			},
		),
		gen(
			"HS256 common key",
			[]string{cndCommon},
			[]string{actCheckNoError},
			&condition{
				typ: v1.SigningKeyType_COMMON,
				alg: HS256,
				b:   []byte(testCommonKey),
			},
			&action{
				key: []byte(testCommonKey),
			},
		),
		gen(
			"HS384 common key",
			[]string{cndCommon},
			[]string{actCheckNoError},
			&condition{
				typ: v1.SigningKeyType_COMMON,
				alg: HS384,
				b:   []byte(testCommonKey),
			},
			&action{
				key: []byte(testCommonKey),
			},
		),
		gen(
			"HS512 common key",
			[]string{cndCommon},
			[]string{actCheckNoError},
			&condition{
				typ: v1.SigningKeyType_COMMON,
				alg: HS512,
				b:   []byte(testCommonKey),
			},
			&action{
				key: []byte(testCommonKey),
			},
		),
		gen(
			"NONE",
			[]string{},
			[]string{actCheckNoError},
			&condition{
				typ: v1.SigningKeyType_KEY_TYPE_UNKNOWN,
				alg: NONE,
				b:   []byte(""),
			},
			&action{
				key: jwt.UnsafeAllowNoneSignatureType,
			},
		),
		gen(
			"invalid type",
			[]string{cndInvalidType},
			[]string{actCheckError},
			&condition{
				typ: v1.SigningKeyType_KEY_TYPE_UNKNOWN,
				b:   []byte("test"),
			},
			&action{
				err: ErrInvalidType,
			},
		),
		gen(
			"key is empty",
			[]string{cndEmptyKey, cndCommon},
			[]string{actCheckError},
			&condition{
				typ: v1.SigningKeyType_COMMON,
				alg: HS256,
				b:   []byte(""),
			},
			&action{
				err: ErrNoKey,
			},
		),
	}

	testutil.Register(table, testCases...)

	for _, tt := range table.Entries() {
		tt := tt

		t.Run(tt.Name(), func(t *testing.T) {
			key, err := parseKey(tt.C().typ, tt.C().alg, tt.C().b)

			testutil.Diff(t, tt.A().err, err, cmpopts.EquateErrors())

			if err == nil {
				// Only check key type because it is difficult to compare the content of keys attributes.
				// The validation of the key content should be done through other tests in other way.
				testutil.Diff(t, reflect.TypeOf(tt.A().key).String(), reflect.TypeOf(key).String())
			} else {
				testutil.Diff(t, nil, key)
			}
		})
	}
}

func TestParsePrivateKey(t *testing.T) {
	type condition struct {
		alg Algorithm
		b   []byte
	}

	type action struct {
		key any
		err any // error or errorutil.Kind
	}

	cndRS := "RSxxx algorithm"
	cndPS := "PSxxx algorithm"
	cndES := "ESxxx algorithm"
	cndEdDSA := "EdDSA algorithm"
	cndInvalid := "invalid algorithm"
	actCheckNoError := "check that there is no error"
	actCheckError := "check error"

	tb := testutil.NewTableBuilder[*condition, *action]()
	tb.Name(t.Name())
	tb.Condition(cndRS, "specify one of RS253/RS384/RS512 algorithm")
	tb.Condition(cndPS, "specify one of PS253/PS384/PS512 algorithm")
	tb.Condition(cndES, "specify one of ES253/ES384/ES512 algorithm")
	tb.Condition(cndEdDSA, "specify EdDSA algorithm")
	tb.Condition(cndInvalid, "specify invalid algorithm")
	tb.Action(actCheckNoError, "check that there is no error returned")
	tb.Action(actCheckError, "check that an expected error was returned")
	table := tb.Build()

	gen := testutil.NewCase[*condition, *action]
	testCases := []*testutil.Case[*condition, *action]{
		gen(
			"RS256",
			[]string{cndRS},
			[]string{actCheckNoError},
			&condition{
				alg: RS256,
				b:   []byte(testRSAPrivateKeyPem),
			},
			&action{
				key: &rsa.PrivateKey{},
			},
		),
		gen(
			"RS384",
			[]string{cndRS},
			[]string{actCheckNoError},
			&condition{
				alg: RS384,
				b:   []byte(testRSAPrivateKeyPem),
			},
			&action{
				key: &rsa.PrivateKey{},
			},
		),
		gen(
			"RS512",
			[]string{cndRS},
			[]string{actCheckNoError},
			&condition{
				alg: RS512,
				b:   []byte(testRSAPrivateKeyPem),
			},
			&action{
				key: &rsa.PrivateKey{},
			},
		),
		gen(
			"PS256",
			[]string{cndPS},
			[]string{actCheckNoError},
			&condition{
				alg: PS256,
				b:   []byte(testRSAPrivateKeyPem),
			},
			&action{
				key: &rsa.PrivateKey{},
			},
		),
		gen(
			"PS384",
			[]string{cndPS},
			[]string{actCheckNoError},
			&condition{
				alg: PS384,
				b:   []byte(testRSAPrivateKeyPem),
			},
			&action{
				key: &rsa.PrivateKey{},
			},
		),
		gen(
			"PS512",
			[]string{cndPS},
			[]string{actCheckNoError},
			&condition{
				alg: PS512,
				b:   []byte(testRSAPrivateKeyPem),
			},
			&action{
				key: &rsa.PrivateKey{},
			},
		),
		gen(
			"ES256",
			[]string{cndES},
			[]string{actCheckNoError},
			&condition{
				alg: ES256,
				b:   []byte(testECPrivateKeyPem),
			},
			&action{
				key: &ecdsa.PrivateKey{},
			},
		),
		gen(
			"ES384",
			[]string{cndES},
			[]string{actCheckNoError},
			&condition{
				alg: ES384,
				b:   []byte(testECPrivateKeyPem),
			},
			&action{
				key: &ecdsa.PrivateKey{},
			},
		),
		gen(
			"ES512",
			[]string{cndES},
			[]string{actCheckNoError},
			&condition{
				alg: ES512,
				b:   []byte(testECPrivateKeyPem),
			},
			&action{
				key: &ecdsa.PrivateKey{},
			},
		),
		gen(
			"EdDSA",
			[]string{cndEdDSA},
			[]string{actCheckNoError},
			&condition{
				alg: EdDSA,
				b:   []byte(testEDPrivateKeyPem),
			},
			&action{
				key: ed25519.PrivateKey{},
			},
		),
		gen(
			"invalid alg",
			[]string{cndInvalid},
			[]string{actCheckError},
			&condition{
				alg: Algorithm("INVALID"),
				b:   []byte(""),
			},
			&action{
				err: ErrInvalidAlg,
			},
		),
	}

	testutil.Register(table, testCases...)

	for _, tt := range table.Entries() {
		tt := tt

		t.Run(tt.Name(), func(t *testing.T) {
			key, err := parsePrivateKey(tt.C().alg, tt.C().b)

			testutil.Diff(t, tt.A().err, err, cmpopts.EquateErrors())

			if err == nil {
				// Only check key type because it is difficult to compare the content of keys attributes.
				// The validation of the key content should be done through other tests in other way.
				testutil.Diff(t, reflect.TypeOf(tt.A().key).String(), reflect.TypeOf(key).String())
			} else {
				testutil.Diff(t, nil, key)
			}
		})
	}
}

func TestParsePublicKey(t *testing.T) {
	type condition struct {
		alg Algorithm
		b   []byte
	}

	type action struct {
		key any
		err any // error or errorutil.Kind
	}

	cndRS := "RSxxx algorithm"
	cndPS := "PSxxx algorithm"
	cndES := "ESxxx algorithm"
	cndEdDSA := "EdDSA algorithm"
	cndInvalid := "invalid algorithm"
	actCheckNoError := "check that there is no error"
	actCheckError := "check error"

	tb := testutil.NewTableBuilder[*condition, *action]()
	tb.Name(t.Name())
	tb.Condition(cndRS, "specify one of RS253/RS384/RS512 algorithm")
	tb.Condition(cndPS, "specify one of PS253/PS384/PS512 algorithm")
	tb.Condition(cndES, "specify one of ES253/ES384/ES512 algorithm")
	tb.Condition(cndEdDSA, "specify EdDSA algorithm")
	tb.Condition(cndInvalid, "specify invalid algorithm")
	tb.Action(actCheckNoError, "check that there is no error returned")
	tb.Action(actCheckError, "check that an expected error was returned")
	table := tb.Build()

	gen := testutil.NewCase[*condition, *action]
	testCases := []*testutil.Case[*condition, *action]{
		gen(
			"RS256",
			[]string{cndRS},
			[]string{actCheckNoError},
			&condition{
				alg: RS256,
				b:   []byte(testRSAPublicKeyPem),
			},
			&action{
				key: &rsa.PublicKey{},
			},
		),
		gen(
			"RS384",
			[]string{cndRS},
			[]string{actCheckNoError},
			&condition{
				alg: RS384,
				b:   []byte(testRSAPublicKeyPem),
			},
			&action{
				key: &rsa.PublicKey{},
			},
		),
		gen(
			"RS512",
			[]string{cndRS},
			[]string{actCheckNoError},
			&condition{
				alg: RS512,
				b:   []byte(testRSAPublicKeyPem),
			},
			&action{
				key: &rsa.PublicKey{},
			},
		),
		gen(
			"PS256",
			[]string{cndPS},
			[]string{actCheckNoError},
			&condition{
				alg: PS256,
				b:   []byte(testRSAPublicKeyPem),
			},
			&action{
				key: &rsa.PublicKey{},
			},
		),
		gen(
			"PS384",
			[]string{cndPS},
			[]string{actCheckNoError},
			&condition{
				alg: PS384,
				b:   []byte(testRSAPublicKeyPem),
			},
			&action{
				key: &rsa.PublicKey{},
			},
		),
		gen(
			"PS512",
			[]string{cndPS},
			[]string{actCheckNoError},
			&condition{
				alg: PS512,
				b:   []byte(testRSAPublicKeyPem),
			},
			&action{
				key: &rsa.PublicKey{},
			},
		),
		gen(
			"ES256",
			[]string{cndES},
			[]string{actCheckNoError},
			&condition{
				alg: ES256,
				b:   []byte(testECPublicKeyPem),
			},
			&action{
				key: &ecdsa.PublicKey{},
			},
		),
		gen(
			"ES384",
			[]string{cndES},
			[]string{actCheckNoError},
			&condition{
				alg: ES384,
				b:   []byte(testECPublicKeyPem),
			},
			&action{
				key: &ecdsa.PublicKey{},
			},
		),
		gen(
			"ES512",
			[]string{cndES},
			[]string{actCheckNoError},
			&condition{
				alg: ES512,
				b:   []byte(testECPublicKeyPem),
			},
			&action{
				key: &ecdsa.PublicKey{},
			},
		),
		gen(
			"EdDSA",
			[]string{cndEdDSA},
			[]string{actCheckNoError},
			&condition{
				alg: EdDSA,
				b:   []byte(testEDPublicKeyPem),
			},
			&action{
				key: ed25519.PublicKey{},
			},
		),
		gen(
			"invalid alg",
			[]string{cndInvalid},
			[]string{actCheckError},
			&condition{
				alg: Algorithm("INVALID"),
				b:   []byte(""),
			},
			&action{
				err: ErrInvalidAlg,
			},
		),
	}

	testutil.Register(table, testCases...)

	for _, tt := range table.Entries() {
		tt := tt

		t.Run(tt.Name(), func(t *testing.T) {
			key, err := parsePublicKey(tt.C().alg, tt.C().b)

			testutil.Diff(t, tt.A().err, err, cmpopts.EquateErrors())

			if err == nil {
				// Only check key type because it is difficult to compare the content of keys attributes.
				// The validation of the key content should be done through other tests in other way.
				testutil.Diff(t, reflect.TypeOf(tt.A().key).String(), reflect.TypeOf(key).String())
			} else {
				testutil.Diff(t, nil, key)
			}
		})
	}
}

func TestNewJWTHandler(t *testing.T) {
	type condition struct {
		spec *v1.JWTHandlerSpec
		rt   http.RoundTripper
	}

	type action struct {
		jh         *JWTHandler
		err        any // error or errorutil.Kind
		errPattern *regexp.Regexp
	}

	cndNil := "nil spec"
	cndPublicKey := "public key"
	cndPrivateKey := "private key"
	cndInvalidKey := "invalid key"
	actCheckHandler := "check the returned error"
	actCheckNoError := "check that there is no error"
	actCheckError := "check error"

	tb := testutil.NewTableBuilder[*condition, *action]()
	tb.Name(t.Name())
	tb.Condition(cndNil, "input nil for spec")
	tb.Condition(cndPublicKey, "specify public key")
	tb.Condition(cndPrivateKey, "specify private key")
	tb.Condition(cndInvalidKey, "specify invalid key")
	tb.Action(actCheckHandler, "check that the returned handler has expected values")
	tb.Action(actCheckNoError, "check that there is no error returned")
	tb.Action(actCheckError, "check that an expected error was returned")
	table := tb.Build()

	gen := testutil.NewCase[*condition, *action]
	testCases := []*testutil.Case[*condition, *action]{
		gen(
			"nil",
			[]string{cndNil},
			[]string{actCheckError},
			&condition{
				spec: nil,
			},
			&action{
				err:        core.ErrCoreGenCreateComponent,
				errPattern: regexp.MustCompile(core.ErrPrefix + `failed to create component. failed to create JWT handler`),
			},
		),
		gen(
			"no keys",
			[]string{},
			[]string{actCheckHandler, actCheckNoError},
			&condition{
				spec: &v1.JWTHandlerSpec{},
			},
			&action{
				jh: &JWTHandler{
					rt:          http.DefaultTransport,
					signingKeys: map[string]*SigningKey{},
					validatingKeys: ValidatingKeyStore{
						keys:    []*SigningKey{},
						jkuKeys: map[string][]*SigningKey{},
					},
					jkus: map[string]string{},
				},
			},
		),
		gen(
			"private key",
			[]string{cndPrivateKey},
			[]string{actCheckHandler, actCheckNoError},
			&condition{
				spec: &v1.JWTHandlerSpec{
					PrivateKeys: []*v1.SigningKeySpec{
						{
							KeyID:     "test",
							Algorithm: v1.SigningKeyAlgorithm_HS256,
							KeyType:   v1.SigningKeyType_COMMON,
							KeyString: base64.StdEncoding.EncodeToString([]byte(testCommonKey)),
						},
					},
				},
			},
			&action{
				jh: &JWTHandler{
					rt: http.DefaultTransport,
					signingKeys: map[string]*SigningKey{
						"test": {
							kid: "test",
							header: map[string]any{
								"alg": HS256,
								"kid": "test",
								"typ": "JWT",
							},
							method: jwt.SigningMethodHS256,
						},
					},
					validatingKeys: ValidatingKeyStore{
						keys:    []*SigningKey{},
						jkuKeys: map[string][]*SigningKey{},
					},
					jkus: map[string]string{},
				},
			},
		),
		gen(
			"public key",
			[]string{cndPublicKey},
			[]string{actCheckHandler, actCheckNoError},
			&condition{
				spec: &v1.JWTHandlerSpec{
					PublicKeys: []*v1.SigningKeySpec{
						{
							KeyID:     "test",
							Algorithm: v1.SigningKeyAlgorithm_HS256,
							KeyType:   v1.SigningKeyType_COMMON,
							KeyString: base64.StdEncoding.EncodeToString([]byte(testCommonKey)),
						},
					},
				},
			},
			&action{
				jh: &JWTHandler{
					rt: http.DefaultTransport,
					validatingKeys: ValidatingKeyStore{
						keys: []*SigningKey{
							{
								kid: "test",
								header: map[string]any{
									"alg": HS256,
									"kid": "test",
									"typ": "JWT",
								},
								method: jwt.SigningMethodHS256,
							},
						},
						jkuKeys: map[string][]*SigningKey{},
					},
					signingKeys: map[string]*SigningKey{},
					jkus:        map[string]string{},
				},
			},
		),
		gen(
			"invalid private key",
			[]string{cndPrivateKey, cndInvalidKey},
			[]string{actCheckError},
			&condition{
				spec: &v1.JWTHandlerSpec{
					PrivateKeys: []*v1.SigningKeySpec{
						{
							KeyID:     "test",
							Algorithm: v1.SigningKeyAlgorithm_HS256,
							KeyType:   v1.SigningKeyType_COMMON,
							KeyString: "invalid key",
						},
					},
				},
			},
			&action{
				err:        core.ErrCoreGenCreateComponent,
				errPattern: regexp.MustCompile(core.ErrPrefix + `failed to create component. failed to create JWT handler`),
			},
		),
		gen(
			"invalid public key",
			[]string{cndPublicKey, cndInvalidKey},
			[]string{actCheckError},
			&condition{
				spec: &v1.JWTHandlerSpec{
					PublicKeys: []*v1.SigningKeySpec{
						{
							KeyID:     "test",
							Algorithm: v1.SigningKeyAlgorithm_HS256,
							KeyType:   v1.SigningKeyType_COMMON,
							KeyString: "invalid key",
						},
					},
				},
			},
			&action{
				err:        core.ErrCoreGenCreateComponent,
				errPattern: regexp.MustCompile(core.ErrPrefix + `failed to create component. failed to create JWT handler`),
			},
		),
	}

	testutil.Register(table, testCases...)

	for _, tt := range table.Entries() {
		tt := tt
		t.Run(tt.Name(), func(t *testing.T) {
			jh, err := NewJWTHandler(tt.C().spec, tt.C().rt)
			testutil.DiffError(t, tt.A().err, tt.A().errPattern, err)

			opts := []cmp.Option{
				cmp.AllowUnexported(JWTHandler{}),
				cmpopts.IgnoreUnexported(sync.RWMutex{}, sync.Mutex{}),
				cmp.Comparer(testutil.ComparePointer[http.RoundTripper]),
				cmp.AllowUnexported(SigningKey{}),
				cmpopts.IgnoreFields(SigningKey{}, "key"),
				cmp.AllowUnexported(ValidatingKeyStore{}),
			}
			testutil.Diff(t, tt.A().jh, jh, opts...)
		})
	}
}

func TestTokenWithClaims(t *testing.T) {
	type condition struct {
		spec   *v1.JWTHandlerSpec
		claims jwt.Claims
	}

	type action struct {
		token *jwt.Token
		err   any // error or errorutil.Kind
	}

	cndWithKey := "with private keys"
	actCheckNoError := "check that there is no error"
	actCheckError := "check error"

	tb := testutil.NewTableBuilder[*condition, *action]()
	tb.Name(t.Name())
	tb.Condition(cndWithKey, "specify private keys")
	tb.Action(actCheckNoError, "check that there is no error returned")
	tb.Action(actCheckError, "check that an expected error was returned")
	table := tb.Build()

	gen := testutil.NewCase[*condition, *action]
	testCases := []*testutil.Case[*condition, *action]{
		gen(
			"with no keys",
			[]string{},
			[]string{actCheckError},
			&condition{
				spec: &v1.JWTHandlerSpec{},
			},
			&action{
				err: ErrNoSigningKey,
			},
		),
		gen(
			"with private key",
			[]string{cndWithKey},
			[]string{actCheckNoError},
			&condition{
				spec: &v1.JWTHandlerSpec{
					PrivateKeys: []*v1.SigningKeySpec{
						{
							KeyID:     "test",
							Algorithm: v1.SigningKeyAlgorithm_HS256,
							KeyType:   v1.SigningKeyType_COMMON,
							KeyString: base64.StdEncoding.EncodeToString([]byte(testCommonKey)),
						},
					},
				},
				claims: jwt.MapClaims{
					"xxx": "XXX",
					"yyy": "YYY",
					"zzz": "ZZZ",
				},
			},
			&action{
				token: &jwt.Token{
					Method: jwt.SigningMethodHS256,
					Header: map[string]any{
						"kid": "test",
						"alg": HS256,
						"typ": "JWT",
					},
					Claims: jwt.MapClaims{
						"xxx": "XXX",
						"yyy": "YYY",
						"zzz": "ZZZ",
					},
				},
			},
		),
	}

	testutil.Register(table, testCases...)

	for _, tt := range table.Entries() {
		tt := tt
		t.Run(tt.Name(), func(t *testing.T) {
			jh, err := NewJWTHandler(tt.C().spec, nil)
			testutil.Diff(t, nil, err)

			token, err := jh.TokenWithClaims(tt.C().claims)
			testutil.Diff(t, tt.A().err, err, cmpopts.EquateErrors())

			opts := []cmp.Option{
				cmp.AllowUnexported(jwt.Token{}),
			}
			testutil.Diff(t, tt.A().token, token, opts...)
		})
	}
}

func TestSignedString(t *testing.T) {
	type condition struct {
		spec  *v1.JWTHandlerSpec
		token *jwt.Token
	}

	type action struct {
		token string
		err   any // error or errorutil.Kind
	}

	cndKeyExist := "key exist"
	cndNilToken := "nil token/nil token header"
	cndValidToken := "valid token"
	actCheckNoError := "check that there is no error"
	actCheckError := "check error"

	tb := testutil.NewTableBuilder[*condition, *action]()
	tb.Name(t.Name())
	tb.Condition(cndKeyExist, "a singing key exists in the handler")
	tb.Condition(cndNilToken, "token or token header is nil")
	tb.Condition(cndValidToken, "input a valid token")
	tb.Action(actCheckNoError, "check that there is no error returned")
	tb.Action(actCheckError, "check that an expected error was returned")
	table := tb.Build()

	gen := testutil.NewCase[*condition, *action]
	testCases := []*testutil.Case[*condition, *action]{
		gen(
			"token is nil",
			[]string{cndNilToken},
			[]string{actCheckError},
			&condition{
				spec:  &v1.JWTHandlerSpec{},
				token: nil,
			},
			&action{
				err: ErrNilToken,
			},
		),
		gen(
			"token header is nil",
			[]string{cndNilToken},
			[]string{actCheckError},
			&condition{
				spec:  &v1.JWTHandlerSpec{},
				token: &jwt.Token{},
			},
			&action{
				err: ErrNilToken,
			},
		),
		gen(
			"no kid header",
			[]string{},
			[]string{actCheckError},
			&condition{
				spec: &v1.JWTHandlerSpec{},
				token: &jwt.Token{
					Header: map[string]any{},
				},
			},
			&action{
				err: ErrNoKid,
			},
		),
		gen(
			"key not found",
			[]string{},
			[]string{actCheckError},
			&condition{
				spec: &v1.JWTHandlerSpec{},
				token: &jwt.Token{
					Header: map[string]any{
						"kid": "KEY-NOT-EXIST",
					},
				},
			},
			&action{
				err: ErrNoSigningKey,
			},
		),
		gen(
			"valid token string",
			[]string{cndKeyExist, cndValidToken},
			[]string{actCheckNoError},
			&condition{
				spec: &v1.JWTHandlerSpec{
					PrivateKeys: []*v1.SigningKeySpec{
						{
							KeyID:     "test",
							Algorithm: v1.SigningKeyAlgorithm_HS256,
							KeyType:   v1.SigningKeyType_COMMON,
							KeyString: base64.StdEncoding.EncodeToString([]byte(testCommonKey)),
						},
					},
				},
				token: &jwt.Token{
					Method: jwt.SigningMethodHS256,
					Header: map[string]any{
						"kid": "test",
						"alg": HS256,
						"typ": "JWT",
					},
					Claims: jwt.MapClaims{
						"xxx": "XXX",
						"yyy": "YYY",
						"zzz": "ZZZ",
					},
				},
			},
			&action{
				token: "eyJhbGciOiJIUzI1NiIsImtpZCI6InRlc3QiLCJ0eXAiOiJKV1QifQ.eyJ4eHgiOiJYWFgiLCJ5eXkiOiJZWVkiLCJ6enoiOiJaWloifQ.zkRZ85mgo3vWk9DLNGoV0cNOs052AaKdKdzIuksGQCw",
			},
		),
	}

	testutil.Register(table, testCases...)

	for _, tt := range table.Entries() {
		tt := tt
		t.Run(tt.Name(), func(t *testing.T) {
			jh, err := NewJWTHandler(tt.C().spec, nil)
			testutil.Diff(t, nil, err)

			token, err := jh.SignedString(tt.C().token)
			testutil.Diff(t, tt.A().err, err, cmpopts.EquateErrors())
			testutil.Diff(t, tt.A().token, token)
		})
	}
}

func TestParseWithClaims(t *testing.T) {
	type condition struct {
		spec  *v1.JWTHandlerSpec
		token string
	}

	type action struct {
		token *jwt.Token
		err   error
	}

	cndKeyExist := "key exist"
	cndValidToken := "valid token"
	actCheckNoError := "check that there is no error"
	actCheckError := "check error"

	tb := testutil.NewTableBuilder[*condition, *action]()
	tb.Name(t.Name())
	tb.Condition(cndKeyExist, "a singing key exists in the handler")
	tb.Condition(cndValidToken, "input a valid token")
	tb.Action(actCheckNoError, "check that there is no error returned")
	tb.Action(actCheckError, "check that an expected error was returned")
	table := tb.Build()

	gen := testutil.NewCase[*condition, *action]
	testCases := []*testutil.Case[*condition, *action]{
		gen(
			"valid token string",
			[]string{cndKeyExist, cndValidToken},
			[]string{actCheckNoError},
			&condition{
				spec: &v1.JWTHandlerSpec{
					PublicKeys: []*v1.SigningKeySpec{
						{
							KeyID:     "test",
							Algorithm: v1.SigningKeyAlgorithm_HS256,
							KeyType:   v1.SigningKeyType_COMMON,
							KeyString: base64.StdEncoding.EncodeToString([]byte(testCommonKey)),
						},
					},
				},
				token: "eyJhbGciOiJIUzI1NiIsImtpZCI6InRlc3QiLCJ0eXAiOiJKV1QifQ.eyJ4eHgiOiJYWFgiLCJ5eXkiOiJZWVkiLCJ6enoiOiJaWloifQ.zkRZ85mgo3vWk9DLNGoV0cNOs052AaKdKdzIuksGQCw",
			},
			&action{
				token: &jwt.Token{
					Method: jwt.SigningMethodHS256,
					Header: map[string]any{
						"kid": "test",
						"alg": "HS256",
						"typ": "JWT",
					},
					Claims: jwt.MapClaims{
						"xxx": "XXX",
						"yyy": "YYY",
						"zzz": "ZZZ",
					},
					Raw:   "eyJhbGciOiJIUzI1NiIsImtpZCI6InRlc3QiLCJ0eXAiOiJKV1QifQ.eyJ4eHgiOiJYWFgiLCJ5eXkiOiJZWVkiLCJ6enoiOiJaWloifQ.zkRZ85mgo3vWk9DLNGoV0cNOs052AaKdKdzIuksGQCw",
					Valid: true,
				},
			},
		),
		gen(
			"invalid token string",
			[]string{cndKeyExist},
			[]string{actCheckError},
			&condition{
				spec: &v1.JWTHandlerSpec{
					PublicKeys: []*v1.SigningKeySpec{
						{
							KeyID:     "test",
							Algorithm: v1.SigningKeyAlgorithm_HS256,
							KeyType:   v1.SigningKeyType_COMMON,
							KeyString: base64.StdEncoding.EncodeToString([]byte(testCommonKey)),
						},
					},
				},
				token: "eyJhbGciOiJIUzI1NiIsImtpZCI6InRlc3QiLCJ0eXAiOiJKV1QifQ.eyJ4eHgiOiJYWFgiLCJ5eXkiOiJZWVkiLCJ6enoiOiJaWloifQ.invalid",
			},
			&action{
				token: &jwt.Token{
					Method: jwt.SigningMethodHS256,
					Header: map[string]any{
						"kid": "test",
						"alg": "HS256",
						"typ": "JWT",
					},
					Claims: jwt.MapClaims{
						"xxx": "XXX",
						"yyy": "YYY",
						"zzz": "ZZZ",
					},
					Raw:   "eyJhbGciOiJIUzI1NiIsImtpZCI6InRlc3QiLCJ0eXAiOiJKV1QifQ.eyJ4eHgiOiJYWFgiLCJ5eXkiOiJZWVkiLCJ6enoiOiJaWloifQ.invalid",
					Valid: false,
				},
				err: errors.New("token signature is invalid: signature is invalid"),
			},
		),
	}

	testutil.Register(table, testCases...)

	for _, tt := range table.Entries() {
		tt := tt
		t.Run(tt.Name(), func(t *testing.T) {
			jh, err := NewJWTHandler(tt.C().spec, nil)
			testutil.Diff(t, nil, err)

			claims := jwt.MapClaims{}
			token, err := jh.ParseWithClaims(tt.C().token, claims)
			if err != nil {
				testutil.Diff(t, tt.A().err.Error(), err.Error())
			} else {
				testutil.Diff(t, nil, err)
			}

			opts := []cmp.Option{
				cmpopts.IgnoreFields(jwt.Token{}, "Signature"),
			}
			testutil.Diff(t, tt.A().token, token, opts...)
		})
	}
}

func TestValidMapClaims(t *testing.T) {
	type condition struct {
		spec  *v1.JWTHandlerSpec
		token string
	}

	type action struct {
		claims     jwt.MapClaims
		err        any // error or errorutil.Kind
		errPattern *regexp.Regexp
	}

	cndValidToken := "valid token"
	actCheckNoError := "check that there is no error"
	actCheckError := "check error"

	tb := testutil.NewTableBuilder[*condition, *action]()
	tb.Name(t.Name())
	tb.Condition(cndValidToken, "input a valid token")
	tb.Action(actCheckNoError, "check that there is no error returned")
	tb.Action(actCheckError, "check that an expected error was returned")
	table := tb.Build()

	gen := testutil.NewCase[*condition, *action]
	testCases := []*testutil.Case[*condition, *action]{
		gen(
			"valid token string",
			[]string{cndValidToken},
			[]string{actCheckNoError},
			&condition{
				spec: &v1.JWTHandlerSpec{
					PublicKeys: []*v1.SigningKeySpec{
						{
							KeyID:     "test",
							Algorithm: v1.SigningKeyAlgorithm_HS256,
							KeyType:   v1.SigningKeyType_COMMON,
							KeyString: base64.StdEncoding.EncodeToString([]byte(testCommonKey)),
						},
					},
				},
				token: "eyJhbGciOiJIUzI1NiIsImtpZCI6InRlc3QiLCJ0eXAiOiJKV1QifQ.eyJ4eHgiOiJYWFgiLCJ5eXkiOiJZWVkiLCJ6enoiOiJaWloifQ.zkRZ85mgo3vWk9DLNGoV0cNOs052AaKdKdzIuksGQCw",
			},
			&action{
				claims: jwt.MapClaims{
					"xxx": "XXX",
					"yyy": "YYY",
					"zzz": "ZZZ",
				},
			},
		),
		gen(
			"no keys",
			[]string{cndValidToken},
			[]string{actCheckError},
			&condition{
				spec:  &v1.JWTHandlerSpec{},
				token: "eyJhbGciOiJIUzI1NiIsImtpZCI6InRlc3QiLCJ0eXAiOiJKV1QifQ.eyJ4eHgiOiJYWFgiLCJ5eXkiOiJZWVkiLCJ6enoiOiJaWloifQ.zkRZ85mgo3vWk9DLNGoV0cNOs052AaKdKdzIuksGQCw",
			},
			&action{
				claims:     nil,
				err:        app.ErrAppAuthnParseWithClaims,
				errPattern: regexp.MustCompile(core.ErrPrefix + `failed to parse JWT claims`),
			},
		),
		gen(
			"invalid token string",
			[]string{},
			[]string{actCheckError},
			&condition{
				spec: &v1.JWTHandlerSpec{
					PublicKeys: []*v1.SigningKeySpec{
						{
							KeyID:     "test",
							Algorithm: v1.SigningKeyAlgorithm_HS256,
							KeyType:   v1.SigningKeyType_COMMON,
							KeyString: base64.StdEncoding.EncodeToString([]byte(testCommonKey)),
						},
					},
				},
				// "INVALID" at the head of the token.
				token: "INVALIDeyJhbGciOiJIUzI1NiIsImtpZCI6InRlc3QiLCJ0eXAiOiJKV1QifQ.eyJ4eHgiOiJYWFgiLCJ5eXkiOiJZWVkiLCJ6enoiOiJaWloifQ.zkRZ85mgo3vWk9DLNGoV0cNOs052AaKdKdzIuksGQCw",
			},
			&action{
				claims:     nil,
				err:        app.ErrAppAuthnParseWithClaims,
				errPattern: regexp.MustCompile(core.ErrPrefix + `failed to parse JWT claims`),
			},
		),
		gen(
			"invalid signature",
			[]string{},
			[]string{actCheckError},
			&condition{
				spec: &v1.JWTHandlerSpec{
					PublicKeys: []*v1.SigningKeySpec{
						{
							KeyID:     "test",
							Algorithm: v1.SigningKeyAlgorithm_HS256,
							KeyType:   v1.SigningKeyType_COMMON,
							KeyString: base64.StdEncoding.EncodeToString([]byte(testCommonKey)),
						},
					},
				},
				// "INVALID" at the last of the token.
				token: "eyJhbGciOiJIUzI1NiIsImtpZCI6InRlc3QiLCJ0eXAiOiJKV1QifQ.eyJ4eHgiOiJYWFgiLCJ5eXkiOiJZWVkiLCJ6enoiOiJaWloifQ.zkRZ85mgo3vWk9DLNGoV0cNOs052AaKdKdzIINVALID",
			},
			&action{
				claims:     nil,
				err:        app.ErrAppAuthnParseWithClaims,
				errPattern: regexp.MustCompile(core.ErrPrefix + `failed to parse JWT claims`),
			},
		),
	}

	testutil.Register(table, testCases...)

	for _, tt := range table.Entries() {
		tt := tt
		t.Run(tt.Name(), func(t *testing.T) {
			jh, err := NewJWTHandler(tt.C().spec, nil)
			testutil.Diff(t, nil, err)

			claims, err := jh.ValidMapClaims(tt.C().token)
			testutil.DiffError(t, tt.A().err, tt.A().errPattern, err)
			testutil.Diff(t, tt.A().claims, claims)
		})
	}
}

func TestKeyFunc(t *testing.T) {
	type condition struct {
		spec  *v1.JWTHandlerSpec
		token *jwt.Token
	}

	type action struct {
		key any
		err any // error or errorutil.Kind
	}

	cndKeyExist := "key exist"
	cndValidToken := "valid token"
	actCheckNoError := "check that there is no error"
	actCheckError := "check error"

	tb := testutil.NewTableBuilder[*condition, *action]()
	tb.Name(t.Name())
	tb.Condition(cndKeyExist, "a singing key exists in the handler")
	tb.Condition(cndValidToken, "input a valid token")
	tb.Action(actCheckNoError, "check that there is no error returned")
	tb.Action(actCheckError, "check that an expected error was returned")
	table := tb.Build()

	gen := testutil.NewCase[*condition, *action]
	testCases := []*testutil.Case[*condition, *action]{
		gen(
			"valid token string",
			[]string{cndKeyExist, cndValidToken},
			[]string{actCheckNoError},
			&condition{
				spec: &v1.JWTHandlerSpec{
					PublicKeys: []*v1.SigningKeySpec{
						{
							KeyID:     "test",
							Algorithm: v1.SigningKeyAlgorithm_HS256,
							KeyType:   v1.SigningKeyType_COMMON,
							KeyString: base64.StdEncoding.EncodeToString([]byte(testCommonKey)),
						},
					},
				},
				token: &jwt.Token{
					Method: jwt.SigningMethodHS256,
					Header: map[string]any{
						"kid": "test",
						"alg": "HS256",
						"typ": "JWT",
					},
				},
			},
			&action{
				key: []byte(""),
			},
		),
		gen(
			"no kid header",
			[]string{cndKeyExist},
			[]string{actCheckError},
			&condition{
				spec: &v1.JWTHandlerSpec{
					PublicKeys: []*v1.SigningKeySpec{
						{
							KeyID:     "test",
							Algorithm: v1.SigningKeyAlgorithm_HS256,
							KeyType:   v1.SigningKeyType_COMMON,
							KeyString: base64.StdEncoding.EncodeToString([]byte(testCommonKey)),
						},
					},
				},
				token: &jwt.Token{
					Method: jwt.SigningMethodHS256,
					Header: map[string]any{
						"alg": "HS256",
						"typ": "JWT",
					},
				},
			},
			&action{
				key: jwt.VerificationKeySet{
					Keys: []jwt.VerificationKey{},
				},
			},
		),
		gen(
			"no kid header and no kid key",
			[]string{cndKeyExist},
			[]string{actCheckError},
			&condition{
				spec: &v1.JWTHandlerSpec{
					PublicKeys: []*v1.SigningKeySpec{
						{
							Algorithm: v1.SigningKeyAlgorithm_HS256,
							KeyType:   v1.SigningKeyType_COMMON,
							KeyString: base64.StdEncoding.EncodeToString([]byte(testCommonKey)),
						},
					},
				},
				token: &jwt.Token{
					Method: jwt.SigningMethodHS256,
					Header: map[string]any{
						"alg": "HS256",
						"typ": "JWT",
					},
				},
			},
			&action{
				key: jwt.VerificationKeySet{
					Keys: []jwt.VerificationKey{[]uint8("test common key")},
				},
			},
		),
		gen(
			"no key in the handler",
			[]string{cndValidToken},
			[]string{actCheckError},
			&condition{
				spec: &v1.JWTHandlerSpec{
					PublicKeys: []*v1.SigningKeySpec{},
				},
				token: &jwt.Token{
					Method: jwt.SigningMethodHS256,
					Header: map[string]any{
						"kid": "test",
						"alg": "HS256",
						"typ": "JWT",
					},
					Claims: jwt.MapClaims{},
				},
			},
			&action{
				key: []byte(""),
				err: ErrKeyNotFound,
			},
		),
		gen(
			"no alg header",
			[]string{cndKeyExist},
			[]string{actCheckError},
			&condition{
				spec: &v1.JWTHandlerSpec{
					PublicKeys: []*v1.SigningKeySpec{
						{
							KeyID:     "test",
							Algorithm: v1.SigningKeyAlgorithm_HS256,
							KeyType:   v1.SigningKeyType_COMMON,
							KeyString: base64.StdEncoding.EncodeToString([]byte(testCommonKey)),
						},
					},
				},
				token: &jwt.Token{
					Method: jwt.SigningMethodHS256,
					Header: map[string]any{
						"kid": "test",
						"typ": "JWT",
					},
				},
			},
			&action{
				key: []byte(""),
				err: ErrNoAlg,
			},
		),
		gen(
			"invalid algorithm",
			[]string{cndKeyExist},
			[]string{actCheckError},
			&condition{
				spec: &v1.JWTHandlerSpec{
					PublicKeys: []*v1.SigningKeySpec{
						{
							KeyID:     "test",
							Algorithm: v1.SigningKeyAlgorithm_HS256,
							KeyType:   v1.SigningKeyType_COMMON,
							KeyString: base64.StdEncoding.EncodeToString([]byte(testCommonKey)),
						},
					},
				},
				token: &jwt.Token{
					Method: jwt.SigningMethodHS256,
					Header: map[string]any{
						"kid": "test",
						"alg": "INVALID",
						"typ": "JWT",
					},
				},
			},
			&action{
				key: []byte(""),
				err: ErrWrongAlg,
			},
		),
	}

	testutil.Register(table, testCases...)

	for _, tt := range table.Entries() {
		tt := tt
		t.Run(tt.Name(), func(t *testing.T) {
			jh, err := NewJWTHandler(tt.C().spec, nil)
			testutil.Diff(t, nil, err)

			key, err := jh.keyFunc(tt.C().token)
			testutil.Diff(t, tt.A().err, err, cmpopts.EquateErrors())

			if err == nil {
				switch k := key.(type) {
				case jwt.VerificationKeySet:
					testutil.Diff(t, tt.A().key, k)
				default:
					// Only check key type because it is difficult to compare the content of keys attributes.
					// The validation of the key content should be done through other tests in other way.
					testutil.Diff(t, reflect.TypeOf(tt.A().key).String(), reflect.TypeOf(key).String())
				}
			} else {
				testutil.Diff(t, nil, key)
			}
		})
	}
}

type testJWKRoundTripper struct {
	err    error
	status int
	header http.Header
	body   []byte
}

func (rt *testJWKRoundTripper) RoundTrip(r *http.Request) (*http.Response, error) {
	if rt.err != nil {
		return nil, rt.err
	}
	return &http.Response{
		StatusCode:    rt.status,
		ContentLength: int64(len(rt.body)),
		Body:          io.NopCloser(bytes.NewReader(rt.body)),
	}, nil
}

func TestRefreshValidatingKeys(t *testing.T) {
	type condition struct {
		spec  *v1.JWTHandlerSpec
		token *jwt.Token
		rt    http.RoundTripper
	}

	type action struct {
		jku  string
		err  error
	}

	cndKeyExist := "key exist"
	cndUseJWKs := "use JWKs"
	cndUseJKU := "use JKU"
	actCheckJKU := "check returned jku"

	tb := testutil.NewTableBuilder[*condition, *action]()
	tb.Name(t.Name())
	tb.Condition(cndKeyExist, "a singing key exists in the handler")
	tb.Condition(cndUseJWKs, "fetch JWK set using preset JWKs endpoints")
	tb.Condition(cndUseJKU, "fetch JWK set using jku header in a JWT")
	tb.Action(actCheckJKU, "check the returned jku")
	table := tb.Build()

	gen := testutil.NewCase[*condition, *action]
	testCases := []*testutil.Case[*condition, *action]{
		gen(
			"use preset jku",
			[]string{cndUseJWKs},
			[]string{actCheckJKU},
			&condition{
				spec: &v1.JWTHandlerSpec{
					JWKs: map[string]string{
						"test-issuer": "http://test.com/jwks",
					},
				},
				token: &jwt.Token{
					Method: jwt.SigningMethodRS256,
					Header: map[string]any{
						"kid": "test",
						"alg": "RS256",
						"typ": "JWT",
					},
					Claims: jwt.MapClaims{
						"iss": "test-issuer",
					},
				},
				rt: &testJWKRoundTripper{
					status: http.StatusOK,
					header: http.Header{
						"Content-Type": []string{"application/json"},
					},
					body: []byte(testJWKSet),
				},
			},
			&action{
				err:  nil,
				jku:  "http://test.com/jwks",
			},
		),
		gen(
			"use jku in header",
			[]string{cndUseJKU},
			[]string{actCheckJKU},
			&condition{
				spec: &v1.JWTHandlerSpec{
					UseJKU: true,
				},
				token: &jwt.Token{
					Method: jwt.SigningMethodRS256,
					Header: map[string]any{
						"kid": "test",
						"alg": "RS256",
						"typ": "JWT",
						"jku": "http://test.com/jwks",
					},
					Claims: jwt.MapClaims{},
				},
				rt: &testJWKRoundTripper{
					status: http.StatusOK,
					header: http.Header{
						"Content-Type": []string{"application/json"},
					},
					body: []byte(testJWKSet),
				},
			},
			&action{
				err:  nil,
				jku:  "http://test.com/jwks",
			},
		),
		gen(
			"failed to fetch public keys",
			[]string{cndUseJWKs},
			[]string{},
			&condition{
				spec: &v1.JWTHandlerSpec{
					JWKs: map[string]string{
						"test-issuer": "http://test.com/jwks",
					},
				},
				token: &jwt.Token{
					Method: jwt.SigningMethodRS256,
					Header: map[string]any{
						"kid": "test",
						"alg": "RS256",
						"typ": "JWT",
					},
					Claims: jwt.MapClaims{
						"iss": "test-issuer",
					},
				},
				rt: &testJWKRoundTripper{
					// Return an error to make the roundTripper fail.
					err: http.ErrHandlerTimeout,
				},
			},
			&action{
				err:  ErrRefreshValidatingKeys,
				jku:  "",
			},
		),
		gen(
			"key already exists",
			[]string{cndUseJWKs, cndKeyExist},
			[]string{actCheckJKU},
			&condition{
				spec: &v1.JWTHandlerSpec{
					PublicKeys: []*v1.SigningKeySpec{
						{
							KeyID:     "test",
							Algorithm: v1.SigningKeyAlgorithm_RS256,
							KeyType:   v1.SigningKeyType_PUBLIC,
							KeyString: base64.StdEncoding.EncodeToString([]byte(testRSAPublicKeyPem)),
						},
					},
					JWKs: map[string]string{
						"test-issuer": "http://test.com/jwks",
					},
				},
				token: &jwt.Token{
					Method: jwt.SigningMethodRS256,
					Header: map[string]any{
						"kid": "test",
						"alg": "RS256",
						"typ": "JWT",
					},
					Claims: jwt.MapClaims{
						"iss": "test-issuer",
					},
				},
				rt: &testJWKRoundTripper{
					status: http.StatusOK,
					header: http.Header{
						"Content-Type": []string{"application/json"},
					},
					body: []byte(testJWKSet),
				},
			},
			&action{
				err:  nil,
				jku:  "http://test.com/jwks",
			},
		),
		gen(
			"key not found in the JWKs",
			[]string{cndUseJWKs},
			[]string{actCheckJKU},
			&condition{
				spec: &v1.JWTHandlerSpec{
					JWKs: map[string]string{
						"test-issuer": "http://test.com/jwks",
					},
				},
				token: &jwt.Token{
					Method: jwt.SigningMethodRS256,
					Header: map[string]any{
						"kid": "not-exist-in-the-jwks",
						"alg": "RS256",
						"typ": "JWT",
					},
					Claims: jwt.MapClaims{
						"iss": "test-issuer",
					},
				},
				rt: &testJWKRoundTripper{
					status: http.StatusOK,
					header: http.Header{
						"Content-Type": []string{"application/json"},
					},
					body: []byte(testJWKSet),
				},
			},
			&action{
				err:  nil,
				jku:  "http://test.com/jwks",
			},
		),
		gen(
			"delete old keys",
			[]string{cndUseJWKs},
			[]string{actCheckJKU},
			&condition{
				spec: &v1.JWTHandlerSpec{
					JWKs: map[string]string{
						"test-issuer": "http://test.com/jwks",
					},
				},
				token: &jwt.Token{
					Method: jwt.SigningMethodRS256,
					Header: map[string]any{
						"kid": "test",
						"alg": "RS256",
						"typ": "JWT",
					},
					Claims: jwt.MapClaims{
						"iss": "test-issuer",
					},
				},
				rt: &testJWKRoundTripper{
					status: http.StatusOK,
					header: http.Header{
						"Content-Type": []string{"application/json"},
					},
					body: []byte(testJWKSet),
				},
			},
			&action{
				err:  nil,
				jku:  "http://test.com/jwks",
			},
		),
	}

	testutil.Register(table, testCases...)

	for _, tt := range table.Entries() {
		tt := tt
		t.Run(tt.Name(), func(t *testing.T) {
			// Create a new JWT handler from test spec.
			jh, err := NewJWTHandler(tt.C().spec, tt.C().rt)
			testutil.Diff(t, nil, err)

			jku, err := jh.refreshValidatingKeys(tt.C().token)
			testutil.Diff(t, tt.A().err, err, cmpopts.EquateErrors())

			if tt.A().err != nil {
				// Only check key type because it is difficult to compare the content of keys attributes.
				// The validation of the key content should be done through other tests in other way.
				testutil.Diff(t, tt.A().jku, jku)
				// testutil.Diff(t, reflect.TypeOf(tt.A().key).String(), reflect.TypeOf(jh.validatingKeys.keys).String())
			}
		})
	}
}

func TestFetchPublicKeys(t *testing.T) {
	type condition struct {
		jku string
		rt  http.RoundTripper
	}

	type action struct {
		keys       []*SigningKey
		err        any // error or errorutil.Kind
		errPattern *regexp.Regexp
	}

	cndValidJku := "valid JWK set URL"
	cndValidJWKSet := "valid JWK set"
	cndRoundTripFail := "round trip failed"
	actCheckJKU := "check returned keys"
	actCheckNoError := "check that there is no error"
	actCheckError := "check error"

	tb := testutil.NewTableBuilder[*condition, *action]()
	tb.Name(t.Name())
	tb.Condition(cndValidJku, "specify a valid JWK set endpoint URL")
	tb.Condition(cndValidJWKSet, "valid JWK set json was returned")
	tb.Condition(cndRoundTripFail, "failed to round trip")
	tb.Action(actCheckJKU, "check the returned signing keys")
	tb.Action(actCheckNoError, "check that there is no error returned")
	tb.Action(actCheckError, "check that an expected error was returned")
	table := tb.Build()

	gen := testutil.NewCase[*condition, *action]
	testCases := []*testutil.Case[*condition, *action]{
		gen(
			"valid jku and JWKs",
			[]string{cndValidJku, cndValidJWKSet},
			[]string{actCheckJKU, actCheckNoError},
			&condition{
				jku: "http://test.com/jwks",
				rt: &testJWKRoundTripper{
					status: http.StatusOK,
					header: http.Header{
						"Content-Type": []string{"application/json"},
					},
					body: []byte(testJWKSet),
				},
			},
			&action{
				keys: []*SigningKey{
					{
						kid: "test",
						header: map[string]any{
							"kid": "test",
							"alg": "RS256",
							"typ": "JWT",
						},
						method: jwt.SigningMethodRS256,
					},
				},
			},
		),
		gen(
			"invalid jku URL",
			[]string{cndValidJWKSet},
			[]string{actCheckJKU, actCheckError},
			&condition{
				jku: "http://test.com\n",
				rt: &testJWKRoundTripper{
					status: http.StatusOK,
					header: http.Header{
						"Content-Type": []string{"application/json"},
					},
					body: []byte(testJWKSet),
				},
			},
			&action{
				keys:       nil,
				err:        app.ErrAppUtilGetJWKSet,
				errPattern: regexp.MustCompile(core.ErrPrefix + `failed to get JWK sets from JWKs endpoint`),
			},
		),
		gen(
			"error on roundTrip",
			[]string{cndValidJku, cndRoundTripFail},
			[]string{actCheckJKU, actCheckError},
			&condition{
				jku: "http://test.com/jwks",
				rt: &testJWKRoundTripper{
					err: http.ErrHandlerTimeout,
				},
			},
			&action{
				keys:       nil,
				err:        app.ErrAppUtilGetJWKSet,
				errPattern: regexp.MustCompile(core.ErrPrefix + `failed to get JWK sets from JWKs endpoint`),
			},
		),
		gen(
			"invalid JWKs body",
			[]string{cndValidJku},
			[]string{actCheckJKU, actCheckError},
			&condition{
				jku: "http://test.com/jwks",
				rt: &testJWKRoundTripper{
					status: http.StatusOK,
					header: http.Header{
						"Content-Type": []string{"application/json"},
					},
					body: []byte("INVALID BODY"),
				},
			},
			&action{
				keys:       nil,
				err:        app.ErrAppUtilGetJWKSet,
				errPattern: regexp.MustCompile(core.ErrPrefix + `failed to get JWK sets from JWKs endpoint`),
			},
		),
		gen(
			"invalid JWKs content",
			[]string{cndValidJku},
			[]string{actCheckJKU, actCheckError},
			&condition{
				jku: "http://test.com/jwks",
				rt: &testJWKRoundTripper{
					status: http.StatusOK,
					header: http.Header{
						"Content-Type": []string{"application/json"},
					},
					body: []byte(`
						{
							"keys": [
								{
									"p": "8Vbfaq3rEZ11NaJ-V8FRz0C1s6bjavXvib_UaM8sB9DGQgY5aDhFvIbcutD6fyUO8g0j0cXzNeLkfStcucp6twTR1ZnStOAV65-KLxzs1aNkiVEplRWTf0ZGAsSSVPBKTFTbBIC83CTxEWVBOPTosGZlYaiA1sjFMoc14roox48",
									"kty": "RSA",
									"q": "6dM9MWtwUtsTRxTS3pURxxtj1HZvkFEtkJtIWV3jualPwRI-5widpXT0sxGcnv0cslOKrtCEXKWW-0PzU3ckkjSimI7ltxCTyW6EUytRjp5fFsAQWsL6emaDWF7JuC3RPPQanRT0WCCoHXzL9ZiNRogOAdasmDL6POL02W7QAOc",
									"d": "INVALIDbKXru7YZNp-1NJtt_t8LguGDkUgTevzUHTbQLR0a6NoUoCa3NKLcxytjqCdsXO_ZZrG1My7THyLVtB61zL9WrMCA7AUb6M4D3r7kSjUcIZ2AN4P-gbZVnAtn2xos0EuYU9qKweOb-TC66KztHOJ02sGoeojTqTrQ4XHwSDcGzMR3tTop_Qw88DhHuy3i2kbU2r1cYas9xXnmTYGgJGFf5Hv_LAiCMpy8AOyys-c2tqcJuXgJY21oozxXOnvsQST0_3IiG-M43vPvWp8mIMnVybBS4KzWQTllgXJ41QXAvLaFkbEF4gYExSM7ouVPFzBopjMP9ADXFF2RX66LPjUQmQ",
									"e": "AQAB",
									"use": "sig",
									"kid": "test",
									"qi": "D6iD83rzfVs4gC71xUw4jSgY9IDuM5q8dnEbEL7Hoa9TEHePyuUuTpGAuOiuqq_gj_3xUHAv7xdwCNR7fGvkl-hxZFkoAmNSWaJF8W8NfcdpGnhFS2GoUVpN8DzMVBRb5qEFygwvGFioJBTv1Zvh6si2SV7IPqb8BW-QylKXFo8",
									"dp": "HiygbnQkSeWH_Ba--TXghqsujLG3CW6KboyfgCM2_BssTqD8mH1AqtupyWnLiA4YGkduTRIU4I3nx4aeZ1Rbl3pwyeYbpCXIV9dwrYdOTT_KmCifrFYwRN-CbWIqcZhBkd2iLKPz-QpdYbFpQDfOyA9laZpp0XD0xuaE5D4mTx0",
									"alg": "RS256",
									"dq": "jS7J7RQVL0IuIqY1KMZThKhKH9gfRsFtRLot9h_Vx52ulUJCDtfJ8ERtF85ThkAIWrhMrX8j3JjzT-XYfxYaU9lKL9lgZKeyxFZhuaIXTYjcm2z-sv61pMBPIEWjgPzRMP-nLh-f5bJaL-vRbwpcizDNXFjVv9RP-gGDm-Hn4BM",
									"n": "3G81ooderiPSEWimMabYLuTFxZqwsWZ3PjRRP6ZJb0MLCKIc5_CKjHe5hoPhKG-jGuQeSAZDaVdr8uksvc1GK6VA-Zi_9rH5SRORTzcxnhbOsopr_I0gbtWJNIdMZxglJiSae0WE0SLK3V2UQ2R9Uq4IDbSBi72y156g9k9W399mrWphqw5HtLoRHoQn3gjKD3b2gUYbSROXSL27hNZZBYVsAnufyE5ASHHM_grk3WjP0-FrrUQrhsMqYMl4xzQHBTyV9akyBimXPqRldzVMTZr1JPUzdtmSWlERx0X4Pw4Ak67bEQ4gJqiMXqi4ybhYRg29vds1zf4nOdqukPwSCQ"
								}
							]
						}
					`),
				},
			},
			&action{
				keys:       nil,
				err:        app.ErrAppUtilGetJWKSet,
				errPattern: regexp.MustCompile(core.ErrPrefix + `failed to get JWK sets from JWKs endpoint`),
			},
		),
	}

	testutil.Register(table, testCases...)

	for _, tt := range table.Entries() {
		tt := tt
		t.Run(tt.Name(), func(t *testing.T) {
			keys, err := fetchPublicKeys(tt.C().rt, tt.C().jku)
			testutil.DiffError(t, tt.A().err, tt.A().errPattern, err)

			opts := []cmp.Option{
				cmp.AllowUnexported(SigningKey{}),
				cmpopts.IgnoreFields(SigningKey{}, "key"),
				cmpopts.SortSlices(func(x, y *SigningKey) bool { return x.kid > y.kid }),
			}
			testutil.Diff(t, tt.A().keys, keys, opts...)
		})
	}
}
