All checks were successful
Co-authored-by: Jeffrey Smith <jasafpro@gmail.com> Co-committed-by: Jeffrey Smith <jasafpro@gmail.com>
140 lines
3.5 KiB
Go
140 lines
3.5 KiB
Go
package handlers
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/json"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/gin-gonic/gin"
|
|
"github.com/golang-jwt/jwt/v5"
|
|
"github.com/google/uuid"
|
|
|
|
"armature/database"
|
|
)
|
|
|
|
const testJWTSecret = "test-secret-key-for-handler-tests"
|
|
|
|
type testHarness struct {
|
|
router *gin.Engine
|
|
t *testing.T
|
|
}
|
|
|
|
func (h *testHarness) request(method, path, token string, body interface{}) *httptest.ResponseRecorder {
|
|
var bodyReader *bytes.Reader
|
|
if body != nil {
|
|
b, _ := json.Marshal(body)
|
|
bodyReader = bytes.NewReader(b)
|
|
} else {
|
|
bodyReader = bytes.NewReader(nil)
|
|
}
|
|
req := httptest.NewRequest(method, path, bodyReader)
|
|
req.Header.Set("Content-Type", "application/json")
|
|
if token != "" {
|
|
req.Header.Set("Authorization", "Bearer "+token)
|
|
}
|
|
w := httptest.NewRecorder()
|
|
h.router.ServeHTTP(w, req)
|
|
return w
|
|
}
|
|
|
|
func makeToken(userID, email, _ string) string {
|
|
claims := Claims{
|
|
UserID: userID,
|
|
Email: email,
|
|
RegisteredClaims: jwt.RegisteredClaims{
|
|
IssuedAt: jwt.NewNumericDate(time.Now()),
|
|
ExpiresAt: jwt.NewNumericDate(time.Now().Add(15 * time.Minute)),
|
|
Issuer: "armature",
|
|
},
|
|
}
|
|
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
|
|
s, _ := token.SignedString([]byte(testJWTSecret))
|
|
return s
|
|
}
|
|
|
|
// seedInsertReturningID inserts a row, pre-generating an id for SQLite.
|
|
// Expects query like: INSERT INTO users (col1, col2, ...) VALUES ($1, $2, ...) RETURNING id
|
|
func seedInsertReturningID(t *testing.T, query string, args ...interface{}) string {
|
|
t.Helper()
|
|
id := uuid.New().String()
|
|
|
|
// Inject id column and value into the query
|
|
// "INSERT INTO users (username, ..." → "INSERT INTO users (id, username, ..."
|
|
q := strings.Replace(query, "(username,", "(id, username,", 1)
|
|
// Strip RETURNING clause
|
|
if idx := strings.Index(q, " RETURNING "); idx > 0 {
|
|
q = q[:idx]
|
|
}
|
|
|
|
if database.IsSQLite() {
|
|
// Convert $N → ?
|
|
for i := len(args) + 1; i >= 1; i-- {
|
|
q = strings.ReplaceAll(q, "$"+itoa(i), "?")
|
|
}
|
|
// Add ? for the injected id column
|
|
q = strings.Replace(q, "VALUES (", "VALUES (?, ", 1)
|
|
newArgs := append([]interface{}{id}, args...)
|
|
_, err := database.TestDB.Exec(q, newArgs...)
|
|
if err != nil {
|
|
t.Fatalf("seedInsertReturningID (sqlite): %v\nquery: %s", err, q)
|
|
}
|
|
return id
|
|
}
|
|
|
|
// Postgres: renumber $N → $N+1, insert $1 for id
|
|
for i := len(args); i >= 1; i-- {
|
|
q = strings.ReplaceAll(q, "$"+itoa(i), "$"+itoa(i+1))
|
|
}
|
|
q = strings.Replace(q, "(id,", "(id,", 1)
|
|
// Add id as first value placeholder
|
|
q = strings.Replace(q, "VALUES (", "VALUES ($1, ", 1)
|
|
newArgs := append([]interface{}{id}, args...)
|
|
_, err := database.TestDB.Exec(q, newArgs...)
|
|
if err != nil {
|
|
t.Fatalf("seedInsertReturningID (pg): %v\nquery: %s", err, q)
|
|
}
|
|
return id
|
|
}
|
|
|
|
func itoa(i int) string {
|
|
if i < 10 {
|
|
return string(rune('0' + i))
|
|
}
|
|
return string(rune('0'+i/10)) + string(rune('0'+i%10))
|
|
}
|
|
|
|
func decode(args ...interface{}) {
|
|
switch len(args) {
|
|
case 2:
|
|
w := args[0].(*httptest.ResponseRecorder)
|
|
json.NewDecoder(w.Body).Decode(args[1])
|
|
case 3:
|
|
t := args[0].(*testing.T)
|
|
t.Helper()
|
|
w := args[1].(*httptest.ResponseRecorder)
|
|
if err := json.NewDecoder(w.Body).Decode(args[2]); err != nil {
|
|
t.Fatalf("decode response: %v", err)
|
|
}
|
|
}
|
|
}
|
|
|
|
// dialectSQL returns query as-is. Placeholder conversion happens in seedInsertReturningID.
|
|
func dialectSQL(query string) string {
|
|
if database.IsSQLite() {
|
|
q := query
|
|
for i := 20; i >= 1; i-- {
|
|
q = strings.ReplaceAll(q, "$"+itoa(i), "?")
|
|
}
|
|
return q
|
|
}
|
|
return query
|
|
}
|
|
|
|
func init() {
|
|
_ = http.StatusOK
|
|
}
|