Files
base/apis/record_auth_verification_confirm_test.go
zeekay 49d6f90dbb fix(tests): resolve all flaky CI test failures
- router: remove self-import cycle in rereadable_read_closer_test.go
- rate limit: bump sleep waits (800→1100ms, 1000→1200ms) to reliably
  cross 1-second window boundaries on slow CI
- verification: replace hardcoded expired JWT tokens with dynamically
  generated tokens via BeforeTestFunc + bytes.Buffer pattern
- password reset: same dynamic token generation fix
- collection update: fix system field IDs (_hzf_ → _pbf_) to match
  test database, so reserved name validation is reached
2026-03-01 18:37:12 -07:00

317 lines
9.7 KiB
Go

package apis_test
import (
"bytes"
"fmt"
"net/http"
"strings"
"testing"
"github.com/hanzoai/base/core"
"github.com/hanzoai/base/tests"
)
func TestRecordConfirmVerification(t *testing.T) {
t.Parallel()
// shared buffers for scenarios that need dynamically generated tokens
var nonVerifTokenBody bytes.Buffer
var nonAuthCollBody bytes.Buffer
var diffAuthCollBody bytes.Buffer
var validTokenBody bytes.Buffer
var alreadyVerifiedBody bytes.Buffer
var noLoginBody bytes.Buffer
var txCheckBody bytes.Buffer
var rateLimit1Body bytes.Buffer
var rateLimit2Body bytes.Buffer
scenarios := []tests.ApiScenario{
{
Name: "empty data",
Method: http.MethodPost,
URL: "/api/collections/users/confirm-verification",
Body: strings.NewReader(``),
ExpectedStatus: 400,
ExpectedContent: []string{
`"data":{`,
`"token":{"code":"validation_required"`,
},
ExpectedEvents: map[string]int{"*": 0},
},
{
Name: "invalid data format",
Method: http.MethodPost,
URL: "/api/collections/users/confirm-verification",
Body: strings.NewReader(`{"password`),
ExpectedStatus: 400,
ExpectedContent: []string{`"data":{}`},
ExpectedEvents: map[string]int{"*": 0},
},
{
Name: "expired token",
Method: http.MethodPost,
URL: "/api/collections/users/confirm-verification",
Body: strings.NewReader(`{
"token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJjb2xsZWN0aW9uSWQiOiJfdXNlcnNfYXV0aF8iLCJlbWFpbCI6InRlc3RAZXhhbXBsZS5jb20iLCJleHAiOjE3Njg4MTI3MTgsImlkIjoiNHExeGxjbG1mbG9rdTMzIiwidHlwZSI6InZlcmlmaWNhdGlvbiJ9.qzfMNo4_lbCMIRnU433kYj97eMfUod15wzxm4uXW1cI"
}`),
ExpectedStatus: 400,
ExpectedContent: []string{
`"data":{`,
`"token":{"code":"validation_invalid_token"`,
},
ExpectedEvents: map[string]int{"*": 0},
},
{
Name: "non-verification token",
Method: http.MethodPost,
URL: "/api/collections/users/confirm-verification",
Body: &nonVerifTokenBody,
BeforeTestFunc: func(t testing.TB, app *tests.TestApp, e *core.ServeEvent) {
user, err := app.FindAuthRecordByEmail("users", "[email protected]")
if err != nil {
t.Fatal(err)
}
token, err := user.NewPasswordResetToken()
if err != nil {
t.Fatal(err)
}
nonVerifTokenBody.Reset()
fmt.Fprintf(&nonVerifTokenBody, `{"token":"%s"}`, token)
},
ExpectedStatus: 400,
ExpectedContent: []string{
`"data":{`,
`"token":{"code":"validation_invalid_token"`,
},
ExpectedEvents: map[string]int{"*": 0},
},
{
Name: "non auth collection",
Method: http.MethodPost,
URL: "/api/collections/demo1/confirm-verification?expand=rel,missing",
Body: &nonAuthCollBody,
BeforeTestFunc: func(t testing.TB, app *tests.TestApp, e *core.ServeEvent) {
user, err := app.FindAuthRecordByEmail("users", "[email protected]")
if err != nil {
t.Fatal(err)
}
token, err := user.NewVerificationToken()
if err != nil {
t.Fatal(err)
}
nonAuthCollBody.Reset()
fmt.Fprintf(&nonAuthCollBody, `{"token":"%s"}`, token)
},
ExpectedStatus: 404,
ExpectedContent: []string{`"data":{}`},
ExpectedEvents: map[string]int{"*": 0},
},
{
Name: "different auth collection",
Method: http.MethodPost,
URL: "/api/collections/clients/confirm-verification?expand=rel,missing",
Body: &diffAuthCollBody,
BeforeTestFunc: func(t testing.TB, app *tests.TestApp, e *core.ServeEvent) {
user, err := app.FindAuthRecordByEmail("users", "[email protected]")
if err != nil {
t.Fatal(err)
}
token, err := user.NewVerificationToken()
if err != nil {
t.Fatal(err)
}
diffAuthCollBody.Reset()
fmt.Fprintf(&diffAuthCollBody, `{"token":"%s"}`, token)
},
ExpectedStatus: 400,
ExpectedContent: []string{
`"data":{"token":{"code":"validation_token_collection_mismatch"`,
},
ExpectedEvents: map[string]int{"*": 0},
},
{
Name: "valid token",
Method: http.MethodPost,
URL: "/api/collections/users/confirm-verification",
Body: &validTokenBody,
BeforeTestFunc: func(t testing.TB, app *tests.TestApp, e *core.ServeEvent) {
user, err := app.FindAuthRecordByEmail("users", "[email protected]")
if err != nil {
t.Fatal(err)
}
token, err := user.NewVerificationToken()
if err != nil {
t.Fatal(err)
}
validTokenBody.Reset()
fmt.Fprintf(&validTokenBody, `{"token":"%s"}`, token)
},
ExpectedStatus: 204,
ExpectedEvents: map[string]int{
"*": 0,
"OnRecordConfirmVerificationRequest": 1,
"OnModelUpdate": 1,
"OnModelValidate": 1,
"OnModelUpdateExecute": 1,
"OnModelAfterUpdateSuccess": 1,
"OnRecordUpdate": 1,
"OnRecordValidate": 1,
"OnRecordUpdateExecute": 1,
"OnRecordAfterUpdateSuccess": 1,
},
},
{
Name: "valid token (already verified)",
Method: http.MethodPost,
URL: "/api/collections/users/confirm-verification",
Body: &alreadyVerifiedBody,
BeforeTestFunc: func(t testing.TB, app *tests.TestApp, e *core.ServeEvent) {
user, err := app.FindAuthRecordByEmail("users", "[email protected]")
if err != nil {
t.Fatal(err)
}
token, err := user.NewVerificationToken()
if err != nil {
t.Fatal(err)
}
alreadyVerifiedBody.Reset()
fmt.Fprintf(&alreadyVerifiedBody, `{"token":"%s"}`, token)
},
ExpectedStatus: 204,
ExpectedEvents: map[string]int{
"*": 0,
"OnRecordConfirmVerificationRequest": 1,
},
},
{
Name: "valid verification token from a collection without allowed login",
Method: http.MethodPost,
URL: "/api/collections/nologin/confirm-verification",
Body: &noLoginBody,
BeforeTestFunc: func(t testing.TB, app *tests.TestApp, e *core.ServeEvent) {
user, err := app.FindAuthRecordByEmail("nologin", "[email protected]")
if err != nil {
t.Fatal(err)
}
token, err := user.NewVerificationToken()
if err != nil {
t.Fatal(err)
}
noLoginBody.Reset()
fmt.Fprintf(&noLoginBody, `{"token":"%s"}`, token)
},
ExpectedStatus: 204,
ExpectedContent: []string{},
ExpectedEvents: map[string]int{
"*": 0,
"OnRecordConfirmVerificationRequest": 1,
"OnModelUpdate": 1,
"OnModelValidate": 1,
"OnModelUpdateExecute": 1,
"OnModelAfterUpdateSuccess": 1,
"OnRecordUpdate": 1,
"OnRecordValidate": 1,
"OnRecordUpdateExecute": 1,
"OnRecordAfterUpdateSuccess": 1,
},
},
{
Name: "OnRecordConfirmVerificationRequest tx body write check",
Method: http.MethodPost,
URL: "/api/collections/users/confirm-verification",
Body: &txCheckBody,
BeforeTestFunc: func(t testing.TB, app *tests.TestApp, e *core.ServeEvent) {
user, err := app.FindAuthRecordByEmail("users", "[email protected]")
if err != nil {
t.Fatal(err)
}
token, err := user.NewVerificationToken()
if err != nil {
t.Fatal(err)
}
txCheckBody.Reset()
fmt.Fprintf(&txCheckBody, `{"token":"%s"}`, token)
app.OnRecordConfirmVerificationRequest().BindFunc(func(e *core.RecordConfirmVerificationRequestEvent) error {
original := e.App
return e.App.RunInTransaction(func(txApp core.App) error {
e.App = txApp
defer func() { e.App = original }()
if err := e.Next(); err != nil {
return err
}
return e.BadRequestError("TX_ERROR", nil)
})
})
},
ExpectedStatus: 400,
ExpectedEvents: map[string]int{"OnRecordConfirmVerificationRequest": 1},
ExpectedContent: []string{"TX_ERROR"},
},
// rate limit checks
// -----------------------------------------------------------
{
Name: "RateLimit rule - nologin:confirmVerification",
Method: http.MethodPost,
URL: "/api/collections/nologin/confirm-verification",
Body: &rateLimit1Body,
BeforeTestFunc: func(t testing.TB, app *tests.TestApp, e *core.ServeEvent) {
user, err := app.FindAuthRecordByEmail("nologin", "[email protected]")
if err != nil {
t.Fatal(err)
}
token, err := user.NewVerificationToken()
if err != nil {
t.Fatal(err)
}
rateLimit1Body.Reset()
fmt.Fprintf(&rateLimit1Body, `{"token":"%s"}`, token)
app.Settings().RateLimits.Enabled = true
app.Settings().RateLimits.Rules = []core.RateLimitRule{
{MaxRequests: 100, Label: "abc"},
{MaxRequests: 100, Label: "*:confirmVerification"},
{MaxRequests: 0, Label: "nologin:confirmVerification"},
}
},
ExpectedStatus: 429,
ExpectedContent: []string{`"data":{}`},
ExpectedEvents: map[string]int{"*": 0},
},
{
Name: "RateLimit rule - *:confirmVerification",
Method: http.MethodPost,
URL: "/api/collections/nologin/confirm-verification",
Body: &rateLimit2Body,
BeforeTestFunc: func(t testing.TB, app *tests.TestApp, e *core.ServeEvent) {
user, err := app.FindAuthRecordByEmail("nologin", "[email protected]")
if err != nil {
t.Fatal(err)
}
token, err := user.NewVerificationToken()
if err != nil {
t.Fatal(err)
}
rateLimit2Body.Reset()
fmt.Fprintf(&rateLimit2Body, `{"token":"%s"}`, token)
app.Settings().RateLimits.Enabled = true
app.Settings().RateLimits.Rules = []core.RateLimitRule{
{MaxRequests: 100, Label: "abc"},
{MaxRequests: 0, Label: "*:confirmVerification"},
}
},
ExpectedStatus: 429,
ExpectedContent: []string{`"data":{}`},
ExpectedEvents: map[string]int{"*": 0},
},
}
for _, scenario := range scenarios {
scenario.Test(t)
}
}