Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,16 @@ this project follows [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
CI-owned tag creation.
- Release validation scripts, agent guidance (`AGENTS.md`), and
[`docs/release-checklist.md`](docs/release-checklist.md).
- Internal HS256 JWT parsing for assertions (`internal/jwt`); public JWT types
(`assertjson.JWTToken`, `JWTKeyFunc`, `JWTMapClaims`) and `SignHS256JWT` test helper.

### Changed

- JWT assertion callbacks use `assertjson` types instead of `github.com/golang-jwt/jwt/v5`.

### Removed

- Direct dependency on `github.com/golang-jwt/jwt/v5`.

## [0.11.0] - 2026-02-07

Expand Down
4 changes: 2 additions & 2 deletions EXAMPLES.md
Original file line number Diff line number Diff line change
Expand Up @@ -234,11 +234,11 @@ assertjson.Has(t, data, func(json *assertjson.AssertJSON) {
```go
import (
"time"
"github.com/golang-jwt/jwt/v5"
"github.com/muonsoft/api-testing/assertjson"
)

assertjson.Has(t, data, func(json *assertjson.AssertJSON) {
isJWT := json.Node("jwt").IsJWT(func(token *jwt.Token) (interface{}, error) {
isJWT := json.Node("jwt").IsJWT(func(token *assertjson.JWTToken) (interface{}, error) {
return []byte("your-256-bit-secret"), nil
})
isJWT.
Expand Down
3 changes: 1 addition & 2 deletions apitest/response_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import (
"net/http/httptest"
"testing"

"github.com/golang-jwt/jwt/v5"
"github.com/muonsoft/api-testing/apitest"
"github.com/muonsoft/api-testing/assertjson"
"github.com/muonsoft/api-testing/internal/mock"
Expand Down Expand Up @@ -418,6 +417,6 @@ func TestAssertResponse(t *testing.T) {

const tokenSecret = "your-256-bit-secret"

func getJWTSecret(_ *jwt.Token) (interface{}, error) {
func getJWTSecret(_ *assertjson.JWTToken) (interface{}, error) {
return []byte(tokenSecret), nil
}
37 changes: 23 additions & 14 deletions assertions/jwt.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,25 +7,34 @@ import (
"testing"
"time"

"github.com/golang-jwt/jwt/v5"
"github.com/muonsoft/api-testing/assertjson"
ijwt "github.com/muonsoft/api-testing/internal/jwt"
"github.com/stretchr/testify/assert"
)

// JWTKeyFunc supplies the verification key while parsing a JWT string.
type JWTKeyFunc = assertjson.JWTKeyFunc

// JWTToken is a parsed and verified JWT exposed to test code.
type JWTToken = assertjson.JWTToken

// JWTMapClaims is the decoded JWT payload used in assertions and test helpers.
type JWTMapClaims = assertjson.JWTMapClaims

// JWTAssertion is used to build a chain of assertions for the JWT node.
type JWTAssertion struct {
t TestingT
messagePrefix string
token *jwt.Token
token *ijwt.Token
}

// WithJWT asserts that the JSON node has a string value with JWT.
func (a *StringAssertion) WithJWT(keyFunc jwt.Keyfunc, msgAndArgs ...interface{}) *JWTAssertion {
func (a *StringAssertion) WithJWT(keyFunc JWTKeyFunc, msgAndArgs ...interface{}) *JWTAssertion {
if a == nil {
return nil
}
a.t.Helper()
token, err := jwt.Parse(a.value, keyFunc)
token, err := ijwt.Parse(a.value, adaptAssertionsKeyFunc(keyFunc))
if err == nil {
return &JWTAssertion{t: a.t, messagePrefix: a.messagePrefix, token: token}
}
Expand Down Expand Up @@ -84,7 +93,7 @@ func (a *JWTAssertion) WithPayload(jsonAssert assertjson.JSONAssertFunc) *JWTAss
jsonAssert(assertjson.NewAssertJSON(
a.t,
a.messagePrefix+`is JWT with payload: `,
map[string]interface{}(a.token.Claims.(jwt.MapClaims)),
map[string]interface{}(a.token.Claims),
))

return a
Expand Down Expand Up @@ -163,32 +172,32 @@ func (a *JWTAssertion) WithIssuedAt() *TimeAssertion {
return a.assertTimeField("issued at", "iat")
}

// Value returns decoded jwt.Token. If parsing fails it will return empty struct.
func (a *JWTAssertion) Value() *jwt.Token {
// Value returns decoded JWT. If parsing fails it will return empty struct.
func (a *JWTAssertion) Value() *JWTToken {
if a == nil {
return &jwt.Token{}
return &JWTToken{}
}
a.t.Helper()

return a.token
return assertjson.WrapJWTToken(a.token)
}

// Assert asserts that the JWT is satisfied by the user function assertFunc.
func (a *JWTAssertion) Assert(assertFunc func(tb testing.TB, token *jwt.Token)) *JWTAssertion {
func (a *JWTAssertion) Assert(assertFunc func(tb testing.TB, token *JWTToken)) *JWTAssertion {
if a == nil {
return nil
}
a.t.Helper()

assertFunc(a.t.(testing.TB), a.token)
assertFunc(a.t.(testing.TB), assertjson.WrapJWTToken(a.token))

return a
}

func (a *JWTAssertion) assertStringField(title string, name string, expected string, msgAndArgs ...interface{}) *JWTAssertion {
a.t.Helper()

raw, exist := a.token.Claims.(jwt.MapClaims)[name]
raw, exist := a.token.Claims[name]
if !exist {
return a.failOnMissingField(title, name, strconv.Quote(expected), msgAndArgs...)
}
Expand All @@ -208,7 +217,7 @@ func (a *JWTAssertion) assertStringField(title string, name string, expected str
func (a *JWTAssertion) assertStringsField(title string, name string, expected []string, msgAndArgs ...interface{}) *JWTAssertion {
a.t.Helper()

raw, exist := a.token.Claims.(jwt.MapClaims)[name]
raw, exist := a.token.Claims[name]
if !exist {
return a.failOnMissingField(title, name, wrapArray(formatStrings(expected)), msgAndArgs...)
}
Expand All @@ -226,7 +235,7 @@ func (a *JWTAssertion) assertStringsField(title string, name string, expected []
}

func (a *JWTAssertion) assertTimeField(title string, name string) *TimeAssertion {
raw, exist := a.token.Claims.(jwt.MapClaims)[name]
raw, exist := a.token.Claims[name]
if !exist {
a.failOnMissingField(title, name, "")
return nil
Expand Down
16 changes: 16 additions & 0 deletions assertions/jwt_bridge.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package assertions

import (
"github.com/muonsoft/api-testing/assertjson"
ijwt "github.com/muonsoft/api-testing/internal/jwt"
)

func adaptAssertionsKeyFunc(keyFunc JWTKeyFunc) ijwt.Keyfunc {
if keyFunc == nil {
return nil
}

return func(token *ijwt.Token) (interface{}, error) {
return keyFunc(assertjson.WrapJWTToken(token))
}
}
Loading