Files

233 lines
6.6 KiB
Go

package dto
import (
"testing"
"github.com/gin-gonic/gin/binding"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestTokenDurationValidation(t *testing.T) {
type input struct {
Duration int64 `binding:"omitempty,token_duration"`
}
for _, test := range []struct {
name string
value int64
wantErr bool
}{
{name: "omitted (default)"},
{name: "negative", value: -1, wantErr: true},
{name: "minimum", value: 1},
{name: "custom duration", value: 90},
{name: "maximum", value: 365 * 24 * 60},
{name: "above maximum", value: 365*24*60 + 1, wantErr: true},
} {
t.Run(test.name, func(t *testing.T) {
err := binding.Validator.ValidateStruct(input{Duration: test.value})
if test.wantErr {
require.Error(t, err)
return
}
require.NoError(t, err)
})
}
}
func TestAppConfigJSONValidation(t *testing.T) {
tests := []struct {
name string
valid bool
}{
{
name: "valid string array",
valid: validateJSONStringArray(`["group-id"]`),
},
{
name: "valid custom claims",
valid: validateJSONCustomClaims(`[{"key":"role","value":"user"}]`),
},
{
name: "valid CIMD URL allowlist",
valid: validateCIMDURLAllowlist(`["https://app.example.com/**"]`),
},
{
name: "custom claims object instead of array",
valid: !validateJSONCustomClaims(`{"key":"role","value":"user"}`),
},
{
name: "custom claim missing key",
valid: !validateJSONCustomClaims(`[{"value":"user"}]`),
},
{
name: "non-string group ID",
valid: !validateJSONStringArray(`[42]`),
},
{
name: "null array",
valid: !validateJSONCustomClaims(`null`),
},
{
name: "unsafe CIMD pattern",
valid: !validateCIMDURLAllowlist(`["data:text/html,test"]`),
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
assert.True(t, test.valid)
})
}
}
func TestAppConfigValueTypeValidation(t *testing.T) {
for _, test := range []struct {
name string
valid bool
}{
{name: "true boolean", valid: validateBooleanString("true")},
{name: "false boolean", valid: validateBooleanString("false")},
{name: "non-boolean word", valid: !validateBooleanString("hello")},
{name: "numeric boolean", valid: !validateBooleanString("1")},
{name: "integer", valid: validateIntegerString("60")},
{name: "negative integer", valid: validateIntegerString("-1")},
{name: "decimal", valid: !validateIntegerString("1.5")},
{name: "non-integer word", valid: !validateIntegerString("hello")},
} {
t.Run(test.name, func(t *testing.T) {
assert.True(t, test.valid)
})
}
}
func TestValidateUsername(t *testing.T) {
tests := []struct {
name string
input string
expected bool
}{
{"valid simple", "user123", true},
{"valid with dot", "user.name", true},
{"valid with underscore", "user_name", true},
{"valid with hyphen", "user-name", true},
{"valid with at", "user@name", true},
{"starts with symbol", ".username", false},
{"ends with non-alphanumeric", "username-", false},
{"contains space", "user name", false},
{"valid single char", "a", true},
{"empty", "", false},
{"only special chars", "-._@", false},
{"valid long", "a1234567890_b.c-d@e", true},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
assert.Equal(t, tt.expected, ValidateUsername(tt.input))
})
}
}
func TestValidateClientID(t *testing.T) {
tests := []struct {
name string
input string
expected bool
}{
{"valid simple", "client123", true},
{"valid with dot", "client.id", true},
{"valid with underscore", "client_id", true},
{"valid with hyphen", "client-id", true},
{"valid with all", "client.id-123_abc", true},
{"contains space", "client id", false},
{"contains at", "client@id", false},
{"empty", "", false},
{"only special chars", "-._", true},
{"invalid char", "client!id", false},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
assert.Equal(t, tt.expected, ValidateClientID(tt.input))
})
}
}
func TestValidateResourceURI(t *testing.T) {
tests := []struct {
name string
input string
expected bool
}{
{"valid https URI", "https://api.example.com", true},
{"valid custom scheme URI", "api://PocketID", true},
{"valid URN", "urn:my-app", true},
{"valid query component", "https://api.example.com?tenant=1", true},
{"invalid relative path", "/foo", false},
{"invalid plain string", "foo", false},
{"invalid fragment", "https://api.example.com#tokens", false},
{"invalid empty fragment", "https://api.example.com#", false},
{"invalid unescaped path space", "https://api.example.com/a b", false},
{"invalid unescaped opaque space", "urn:my app", false},
{"invalid malformed URI", "http://[::1", false},
{"invalid javascript scheme", "javascript:alert(1)", false},
{"invalid data scheme", "data:text/html,<script>alert(1)</script>", false},
{"empty", "", false},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
assert.Equal(t, tt.expected, ValidateResourceURI(tt.input))
})
}
}
func TestValidateCallbackURL(t *testing.T) {
tests := []struct {
name string
input string
expected bool
}{
{"valid https URL", "https://example.com/callback", true},
{"valid loopback URL", "http://127.0.0.1:49813/callback", true},
{"empty scheme", "//127.0.0.1:49813/callback", true},
{"valid custom scheme", "pocketid://callback", true},
{"invalid malformed URL", "http://[::1", false},
{"invalid missing scheme separator", "://example.com/callback", false},
{"rejects javascript scheme", "javascript:alert(1)", false},
{"rejects mixed case javascript scheme", "JavaScript:alert(1)", false},
{"rejects data scheme", "data:text/html;base64,PGgxPkhlbGxvPC9oMT4=", false},
{"rejects mixed case data scheme", "DaTa:text/html;base64,PGgxPkhlbGxvPC9oMT4=", false},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
assert.Equal(t, tt.expected, ValidateCallbackURL(tt.input))
})
}
}
func TestValidateCallbackURLPattern(t *testing.T) {
tests := []struct {
name string
input string
expected bool
}{
{"valid exact URL", "https://example.com/callback", true},
{"valid wildcard URL", "https://*.example.com/callback", true},
{"valid custom scheme", "pocketid://callback", true},
{"valid global wildcard", "*", true},
{"invalid relative URL", "/callback", false},
{"invalid malformed URL", "http://[::1", false},
{"rejects javascript scheme", "javascript:alert(1)", false},
{"rejects data scheme", "data:text/html;base64,PGgxPkhlbGxvPC9oMT4=", false},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
assert.Equal(t, tt.expected, ValidateCallbackURLPattern(tt.input))
})
}
}