Stripped lots of boilerplate prepare for minimal test branch
This commit is contained in:
@@ -1,22 +0,0 @@
|
||||
# You can override the included template(s) by including variable overrides
|
||||
# SAST customization: https://docs.gitlab.com/ee/user/application_security/sast/#customizing-the-sast-settings
|
||||
# Secret Detection customization: https://docs.gitlab.com/user/application_security/secret_detection/pipeline/configure
|
||||
# Dependency Scanning customization: https://docs.gitlab.com/ee/user/application_security/dependency_scanning/#customizing-the-dependency-scanning-settings
|
||||
# Container Scanning customization: https://docs.gitlab.com/ee/user/application_security/container_scanning/#customizing-the-container-scanning-settings
|
||||
# Note that environment variables can be set in several places
|
||||
# See https://docs.gitlab.com/ee/ci/variables/#cicd-variable-precedence
|
||||
:stages:
|
||||
- test
|
||||
:sast:
|
||||
:stage: test
|
||||
:include:
|
||||
- :template: Security/SAST.gitlab-ci.yml
|
||||
stages:
|
||||
- test
|
||||
- secret-detection
|
||||
variables:
|
||||
SECRET_DETECTION_ENABLED: 'true'
|
||||
secret_detection:
|
||||
stage: secret-detection
|
||||
include:
|
||||
- template: Security/Secret-Detection.gitlab-ci.yml
|
||||
18
Makefile
18
Makefile
@@ -4,24 +4,8 @@
|
||||
|
||||
# Main target
|
||||
run:
|
||||
go run ./cmd
|
||||
|
||||
# Formatting the whole codebase
|
||||
format:
|
||||
gofmt -w ./src
|
||||
gofmt -w ./cmd
|
||||
|
||||
# Test in verbose mode
|
||||
test:
|
||||
go test ./src -v
|
||||
go test ./cmd -v
|
||||
go run ./src
|
||||
|
||||
# Nix stuff
|
||||
nix-run:
|
||||
nix develop --command make run
|
||||
|
||||
nix-test:
|
||||
nix develop --command make test
|
||||
|
||||
nix-format:
|
||||
nix develop --command make format
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
app "gitlab.gwdg.de/qrank/qrank/src"
|
||||
)
|
||||
|
||||
func main() {
|
||||
app.App()
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
// Dummy main function for testing
|
||||
func TestMain(t *testing.T) {}
|
||||
@@ -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