Stripped lots of boilerplate prepare for minimal test branch
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
package src
|
||||
package main
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
|
||||
118
src/auth_test.go
118
src/auth_test.go
@@ -1,118 +0,0 @@
|
||||
package src
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"gorm.io/driver/sqlite"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// setupTestDB initializes an in-memory SQLite DB for tests
|
||||
func setupTestDB(t *testing.T) *gorm.DB {
|
||||
t.Helper()
|
||||
d, err := gorm.Open(sqlite.Open("file::memory:?cache=shared"), &gorm.Config{})
|
||||
if err != nil {
|
||||
t.Fatalf("failed to open test db: %v", err)
|
||||
}
|
||||
if err := d.AutoMigrate(&User{}, &Session{}); err != nil {
|
||||
t.Fatalf("failed to migrate test db: %v", err)
|
||||
}
|
||||
db = d // override global
|
||||
return d
|
||||
}
|
||||
|
||||
// TestFindOrCreateUserByHandle_Email
|
||||
func TestFindOrCreateUserByHandle_Email(t *testing.T) {
|
||||
setupTestDB(t)
|
||||
|
||||
u, err := findOrCreateUserByHandle("test@example.com")
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if u.Email != "test@example.com" {
|
||||
t.Errorf("expected email test@example.com, got %s", u.Email)
|
||||
}
|
||||
|
||||
// Should return same user if called again
|
||||
u2, err := findOrCreateUserByHandle("test@example.com")
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if u.ID != u2.ID {
|
||||
t.Errorf("expected same user ID, got %d and %d", u.ID, u2.ID)
|
||||
}
|
||||
}
|
||||
|
||||
// TestFindOrCreateUserByHandle_Username
|
||||
func TestFindOrCreateUserByHandle_Username(t *testing.T) {
|
||||
setupTestDB(t)
|
||||
|
||||
u, err := findOrCreateUserByHandle("PlayerOne")
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if !strings.Contains(u.Email, "@local") {
|
||||
t.Errorf("expected placeholder email, got %s", u.Email)
|
||||
}
|
||||
if u.Username != "PlayerOne" {
|
||||
t.Errorf("expected username PlayerOne, got %s", u.Username)
|
||||
}
|
||||
}
|
||||
|
||||
// TestGetSessionUserAndRequireAuth
|
||||
func TestGetSessionUserAndRequireAuth(t *testing.T) {
|
||||
setupTestDB(t)
|
||||
|
||||
// Create a user + session
|
||||
user := &User{Email: "auth@example.com", Username: "authuser"}
|
||||
if err := db.Create(user).Error; err != nil {
|
||||
t.Fatalf("failed to create user: %v", err)
|
||||
}
|
||||
session := &Session{Token: "testtoken", UserID: user.ID}
|
||||
if err := db.Create(session).Error; err != nil {
|
||||
t.Fatalf("failed to create session: %v", err)
|
||||
}
|
||||
|
||||
// Make a Gin context with session cookie
|
||||
w := httptest.NewRecorder()
|
||||
c, _ := gin.CreateTestContext(w)
|
||||
req, _ := http.NewRequest("GET", "/", nil)
|
||||
req.AddCookie(&http.Cookie{Name: "session", Value: "testtoken"})
|
||||
c.Request = req
|
||||
|
||||
// Should find user
|
||||
u := getSessionUser(c)
|
||||
if u == nil || u.Email != "auth@example.com" {
|
||||
t.Errorf("expected user auth@example.com, got %+v", u)
|
||||
}
|
||||
|
||||
// Test requireAuth middleware with valid session
|
||||
w = httptest.NewRecorder()
|
||||
c, _ = gin.CreateTestContext(w)
|
||||
req, _ = http.NewRequest("GET", "/", nil)
|
||||
req.AddCookie(&http.Cookie{Name: "session", Value: "testtoken"})
|
||||
c.Request = req
|
||||
c.Next() // prepare pipeline
|
||||
mw := requireAuth()
|
||||
mw(c)
|
||||
|
||||
if c.IsAborted() {
|
||||
t.Errorf("expected request to pass middleware, but it aborted")
|
||||
}
|
||||
|
||||
// Test requireAuth middleware with no cookie
|
||||
w = httptest.NewRecorder()
|
||||
c, _ = gin.CreateTestContext(w)
|
||||
req, _ = http.NewRequest("GET", "/", nil)
|
||||
c.Request = req
|
||||
mw = requireAuth()
|
||||
mw(c)
|
||||
|
||||
if !c.IsAborted() {
|
||||
t.Errorf("expected request to abort when no session, but it passed")
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package src
|
||||
package main
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
@@ -1,30 +1,9 @@
|
||||
package src
|
||||
|
||||
// socqr-mvp: single-binary Go + Gin app with email-magic-link auth (debug links in logs),
|
||||
// SQLite (switchable to Postgres), and four views: user view, enter scores, history, leaderboard.
|
||||
//
|
||||
// Quick start:
|
||||
// go mod init socqr
|
||||
// go get github.com/gin-gonic/gin gorm.io/gorm gorm.io/driver/sqlite gorm.io/driver/postgres
|
||||
// go run .
|
||||
//
|
||||
// Env vars (optional):
|
||||
// DATABASE_URL="postgres://user:pass@host:5432/dbname?sslmode=disable" // if set, uses Postgres; else SQLite file socqr.db
|
||||
// APP_BASE_URL="http://localhost:8080" // used for magic-link generation (defaults to http://localhost:8080)
|
||||
// APP_BIND=":8080" // server bind address (default :8080)
|
||||
// APP_COOKIE_DOMAIN="" // cookie domain (default empty)
|
||||
//
|
||||
// Notes:
|
||||
// - Magic link tokens are valid for 30 days and can be used multiple times during that window (for multi-device sign-in).
|
||||
// - Session cookie is long-lived (~10 years). Delete cookie to sign out.
|
||||
// - Minimal inline-styled HTML (no external CSS or JS) for fast load and pure-Go rendering.
|
||||
// - QR prep: includes a Table model and routes under /t/:slug/enter to prefill table context (no scanner yet).
|
||||
package main
|
||||
|
||||
import (
|
||||
"html/template"
|
||||
"log"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"time"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
@@ -47,27 +26,13 @@ const (
|
||||
|
||||
// ===================== Templates =====================
|
||||
|
||||
var tpl *template.Template
|
||||
|
||||
func init() {
|
||||
wd, err := os.Getwd()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
if filepath.Base(wd) == "src" {
|
||||
wd = filepath.Join(wd, "..") // adjust if running from src dir
|
||||
}
|
||||
path := filepath.Join(wd, "templates", "*.html")
|
||||
|
||||
tpl = template.Must(template.New("").Funcs(template.FuncMap{
|
||||
"fmtTime": func(t time.Time) string { return t.Local().Format("1000-01-01 10:10") },
|
||||
}).ParseGlob(path))
|
||||
}
|
||||
var tpl = template.Must(template.New("").Funcs(template.FuncMap{
|
||||
"fmtTime": func(t time.Time) string { return t.Local().Format("1000-01-01 10:10") },
|
||||
}).ParseGlob("templates/*.html"))
|
||||
|
||||
// ===================== Main =====================
|
||||
|
||||
func App() {
|
||||
func main() {
|
||||
log.SetFlags(log.LstdFlags | log.Lshortfile)
|
||||
|
||||
baseURL = os.Getenv("APP_BASE_URL")
|
||||
@@ -1,4 +1,4 @@
|
||||
package src
|
||||
package main
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
package src
|
||||
package main
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
@@ -104,7 +104,7 @@ func atoiSafe(s string) int {
|
||||
}
|
||||
|
||||
// minimal sscanf to avoid fmt import just for one call
|
||||
func fmtSscanf(s, format string, a *int) (int, error) {
|
||||
func fmtSscanf(s, _ string, a *int) (int, error) {
|
||||
// Only supports "%d"
|
||||
n := 0
|
||||
for i := 0; i < len(s); i++ {
|
||||
|
||||
Reference in New Issue
Block a user