409 lines
12 KiB
Go
409 lines
12 KiB
Go
package main
|
|
|
|
import (
|
|
"net/http/httptest"
|
|
"testing"
|
|
)
|
|
|
|
func TestPublicSignupConfig(t *testing.T) {
|
|
app := testApp(t)
|
|
mux := testMux(app)
|
|
|
|
app.createDepartment(Department{Name: "Setup", Color: "#ff0000"})
|
|
app.createDepartment(Department{Name: "Teardown", Color: "#00ff00"})
|
|
app.db.Exec(`INSERT OR REPLACE INTO config (key, value) VALUES ('volunteer_note_label', 'Who sent you?')`)
|
|
app.db.Exec(`INSERT OR REPLACE INTO config (key, value) VALUES ('volunteer_note_required', 'true')`)
|
|
|
|
w := httptest.NewRecorder()
|
|
mux.ServeHTTP(w, testRequest("GET", "/api/public/signup-config", nil))
|
|
if w.Code != 200 {
|
|
t.Fatalf("expected 200, got %d", w.Code)
|
|
}
|
|
result := parseJSON(t, w)
|
|
depts, ok := result["departments"].([]any)
|
|
if !ok || len(depts) != 2 {
|
|
t.Fatalf("expected 2 departments, got %v", result["departments"])
|
|
}
|
|
if result["volunteer_note_label"] != "Who sent you?" {
|
|
t.Errorf("expected 'Who sent you?', got %v", result["volunteer_note_label"])
|
|
}
|
|
if result["volunteer_note_required"] != true {
|
|
t.Errorf("expected note required true, got %v", result["volunteer_note_required"])
|
|
}
|
|
}
|
|
|
|
func TestPublicSignup(t *testing.T) {
|
|
app := testApp(t)
|
|
mux := testMux(app)
|
|
app.createDepartment(Department{Name: "Setup", Color: "#ff0000"})
|
|
|
|
w := httptest.NewRecorder()
|
|
deptID := 1
|
|
mux.ServeHTTP(w, testRequest("POST", "/api/public/signup", map[string]any{
|
|
"preferred_name": "Titania",
|
|
"email": "titania@example.com",
|
|
"pronouns": "she/they",
|
|
"department_id": deptID,
|
|
}))
|
|
if w.Code != 200 {
|
|
t.Fatalf("expected 200, got %d: %s", w.Code, w.Body.String())
|
|
}
|
|
result := parseJSON(t, w)
|
|
if result["ok"] != true {
|
|
t.Fatalf("expected ok true, got %v", result)
|
|
}
|
|
|
|
// Volunteer should exist
|
|
vol, err := app.getVolunteerByEmail("titania@example.com")
|
|
if err != nil || vol == nil {
|
|
t.Fatal("volunteer not created")
|
|
}
|
|
if vol.PreferredName != "Titania" {
|
|
t.Errorf("preferred_name = %q, want Titania", vol.PreferredName)
|
|
}
|
|
if vol.Pronouns != "she/they" {
|
|
t.Errorf("pronouns = %q, want she/they", vol.Pronouns)
|
|
}
|
|
if vol.ConfirmationToken == nil || *vol.ConfirmationToken == "" {
|
|
t.Error("expected confirmation token to be set")
|
|
}
|
|
if vol.EmailConfirmed {
|
|
t.Error("should not be confirmed yet")
|
|
}
|
|
|
|
// Participant should be auto-created and linked
|
|
if vol.ParticipantID == nil {
|
|
t.Fatal("expected participant to be linked")
|
|
}
|
|
p, _ := app.getParticipant(*vol.ParticipantID)
|
|
if p == nil {
|
|
t.Fatal("linked participant not found")
|
|
}
|
|
if p.Email != "titania@example.com" {
|
|
t.Errorf("participant email = %q, want titania@example.com", p.Email)
|
|
}
|
|
}
|
|
|
|
func TestPublicSignupAutoMatchParticipant(t *testing.T) {
|
|
app := testApp(t)
|
|
mux := testMux(app)
|
|
|
|
// Pre-existing participant
|
|
existing, _ := app.createParticipant(Participant{PreferredName: "Titania Fairweather", Email: "titania@example.com"})
|
|
|
|
w := httptest.NewRecorder()
|
|
mux.ServeHTTP(w, testRequest("POST", "/api/public/signup", map[string]any{
|
|
"preferred_name": "Titania",
|
|
"ticket_name": "Titania Fairweather",
|
|
"email": "titania@example.com",
|
|
}))
|
|
if w.Code != 200 {
|
|
t.Fatalf("expected 200, got %d", w.Code)
|
|
}
|
|
|
|
vol, _ := app.getVolunteerByEmail("titania@example.com")
|
|
if vol == nil {
|
|
t.Fatal("volunteer not created")
|
|
}
|
|
if vol.ParticipantID == nil || *vol.ParticipantID != existing.ID {
|
|
t.Errorf("expected volunteer linked to existing participant %d, got %v", existing.ID, vol.ParticipantID)
|
|
}
|
|
}
|
|
|
|
func TestPublicSignupDuplicateEmail(t *testing.T) {
|
|
app := testApp(t)
|
|
mux := testMux(app)
|
|
|
|
// First signup
|
|
w := httptest.NewRecorder()
|
|
mux.ServeHTTP(w, testRequest("POST", "/api/public/signup", map[string]any{
|
|
"preferred_name": "Titania",
|
|
"email": "titania@example.com",
|
|
}))
|
|
if w.Code != 200 {
|
|
t.Fatalf("first signup: expected 200, got %d", w.Code)
|
|
}
|
|
|
|
// Second signup with same email — should silently succeed
|
|
w = httptest.NewRecorder()
|
|
mux.ServeHTTP(w, testRequest("POST", "/api/public/signup", map[string]any{
|
|
"preferred_name": "Puck",
|
|
"email": "titania@example.com",
|
|
}))
|
|
if w.Code != 200 {
|
|
t.Fatalf("duplicate signup: expected 200, got %d", w.Code)
|
|
}
|
|
result := parseJSON(t, w)
|
|
if result["ok"] != true {
|
|
t.Fatalf("expected ok true for duplicate, got %v", result)
|
|
}
|
|
|
|
// Should still be only one volunteer
|
|
vols, _ := app.listVolunteers("", nil, "")
|
|
if len(vols) != 1 {
|
|
t.Errorf("expected 1 volunteer, got %d", len(vols))
|
|
}
|
|
}
|
|
|
|
func TestPublicSignupMissingFields(t *testing.T) {
|
|
app := testApp(t)
|
|
mux := testMux(app)
|
|
|
|
tests := []struct {
|
|
name string
|
|
body map[string]any
|
|
}{
|
|
{"no name", map[string]any{"email": "a@b.com"}},
|
|
{"no email", map[string]any{"preferred_name": "Titania"}},
|
|
{"empty both", map[string]any{}},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
w := httptest.NewRecorder()
|
|
mux.ServeHTTP(w, testRequest("POST", "/api/public/signup", tt.body))
|
|
if w.Code != 400 {
|
|
t.Errorf("expected 400, got %d", w.Code)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestPublicSignupNoteRequired(t *testing.T) {
|
|
app := testApp(t)
|
|
mux := testMux(app)
|
|
app.db.Exec(`INSERT OR REPLACE INTO config (key, value) VALUES ('volunteer_note_required', 'true')`)
|
|
|
|
w := httptest.NewRecorder()
|
|
mux.ServeHTTP(w, testRequest("POST", "/api/public/signup", map[string]any{
|
|
"preferred_name": "Titania",
|
|
"email": "titania@example.com",
|
|
"note": "",
|
|
}))
|
|
if w.Code != 400 {
|
|
t.Fatalf("expected 400 when note required but empty, got %d", w.Code)
|
|
}
|
|
|
|
// With note provided
|
|
w = httptest.NewRecorder()
|
|
mux.ServeHTTP(w, testRequest("POST", "/api/public/signup", map[string]any{
|
|
"preferred_name": "Titania",
|
|
"email": "titania@example.com",
|
|
"note": "A friend sent me",
|
|
}))
|
|
if w.Code != 200 {
|
|
t.Fatalf("expected 200 with note, got %d: %s", w.Code, w.Body.String())
|
|
}
|
|
}
|
|
|
|
func TestConfirmEmail(t *testing.T) {
|
|
app := testApp(t)
|
|
mux := testMux(app)
|
|
|
|
token := "abc123def456"
|
|
app.createVolunteer(Volunteer{
|
|
Name: "Titania",
|
|
PreferredName: "Titania",
|
|
Email: "titania@example.com",
|
|
ConfirmationToken: &token,
|
|
})
|
|
|
|
w := httptest.NewRecorder()
|
|
mux.ServeHTTP(w, testRequest("POST", "/api/public/confirm", map[string]any{"token": token}))
|
|
if w.Code != 200 {
|
|
t.Fatalf("expected 200, got %d", w.Code)
|
|
}
|
|
result := parseJSON(t, w)
|
|
if result["status"] != "confirmed" {
|
|
t.Errorf("expected confirmed, got %v", result["status"])
|
|
}
|
|
|
|
// Verify volunteer is confirmed
|
|
vol, _ := app.getVolunteerByEmail("titania@example.com")
|
|
if vol == nil || !vol.EmailConfirmed {
|
|
t.Error("volunteer should be email confirmed")
|
|
}
|
|
if vol.ConfirmationToken != nil {
|
|
t.Error("confirmation token should be cleared after confirmation")
|
|
}
|
|
}
|
|
|
|
func TestConfirmEmailInvalid(t *testing.T) {
|
|
app := testApp(t)
|
|
mux := testMux(app)
|
|
|
|
w := httptest.NewRecorder()
|
|
mux.ServeHTTP(w, testRequest("POST", "/api/public/confirm", map[string]any{"token": "nonexistent"}))
|
|
if w.Code != 200 {
|
|
t.Fatalf("expected 200, got %d", w.Code)
|
|
}
|
|
result := parseJSON(t, w)
|
|
if result["status"] != "invalid" {
|
|
t.Errorf("expected invalid, got %v", result["status"])
|
|
}
|
|
}
|
|
|
|
func TestConfirmEmailAlreadyConfirmed(t *testing.T) {
|
|
app := testApp(t)
|
|
mux := testMux(app)
|
|
|
|
token := "abc123def456"
|
|
app.createVolunteer(Volunteer{
|
|
Name: "Titania",
|
|
PreferredName: "Titania",
|
|
Email: "titania@example.com",
|
|
ConfirmationToken: &token,
|
|
})
|
|
|
|
// Confirm first time
|
|
w := httptest.NewRecorder()
|
|
mux.ServeHTTP(w, testRequest("POST", "/api/public/confirm", map[string]any{"token": token}))
|
|
if parseJSON(t, w)["status"] != "confirmed" {
|
|
t.Fatal("first confirm should succeed")
|
|
}
|
|
|
|
// Second confirm with same token should be invalid (token cleared)
|
|
w = httptest.NewRecorder()
|
|
mux.ServeHTTP(w, testRequest("POST", "/api/public/confirm", map[string]any{"token": token}))
|
|
result := parseJSON(t, w)
|
|
if result["status"] != "invalid" {
|
|
t.Errorf("expected invalid after token cleared, got %v", result["status"])
|
|
}
|
|
}
|
|
|
|
func TestConfirmEmailWithSignupsOpen(t *testing.T) {
|
|
app := testApp(t)
|
|
mux := testMux(app)
|
|
|
|
app.db.Exec(`INSERT OR REPLACE INTO config (key, value) VALUES ('shift_signups_open', 'true')`)
|
|
app.baseURL = "https://example.com"
|
|
|
|
participant, _ := app.createParticipant(Participant{PreferredName: "Titania", Email: "titania@example.com"})
|
|
token := "abc123def456"
|
|
app.createVolunteer(Volunteer{
|
|
Name: "Titania",
|
|
PreferredName: "Titania",
|
|
Email: "titania@example.com",
|
|
ParticipantID: &participant.ID,
|
|
ConfirmationToken: &token,
|
|
})
|
|
|
|
w := httptest.NewRecorder()
|
|
mux.ServeHTTP(w, testRequest("POST", "/api/public/confirm", map[string]any{"token": token}))
|
|
if w.Code != 200 {
|
|
t.Fatalf("expected 200, got %d", w.Code)
|
|
}
|
|
result := parseJSON(t, w)
|
|
if result["status"] != "confirmed" {
|
|
t.Fatalf("expected confirmed, got %v", result["status"])
|
|
}
|
|
kioskLink, ok := result["kiosk_link"].(string)
|
|
if !ok || kioskLink == "" {
|
|
t.Error("expected kiosk_link when signups are open")
|
|
}
|
|
|
|
// Volunteer should now have a kiosk_code, no stub ticket created.
|
|
vol, _ := app.getVolunteerByEmail("titania@example.com")
|
|
if vol == nil || vol.KioskCode == nil {
|
|
t.Error("volunteer should have a kiosk_code after confirm with signups open")
|
|
}
|
|
tickets, _ := app.listTickets(&participant.ID, "")
|
|
if len(tickets) != 0 {
|
|
t.Errorf("expected no stub tickets, got %d", len(tickets))
|
|
}
|
|
}
|
|
|
|
func TestPublicSignupTicketNameDoesNotOverwritePreferredName(t *testing.T) {
|
|
app := testApp(t)
|
|
mux := testMux(app)
|
|
|
|
w := httptest.NewRecorder()
|
|
mux.ServeHTTP(w, testRequest("POST", "/api/public/signup", map[string]any{
|
|
"preferred_name": "Titania",
|
|
"ticket_name": "Titania Fairweather",
|
|
"email": "titania@example.com",
|
|
}))
|
|
if w.Code != 200 {
|
|
t.Fatalf("expected 200, got %d: %s", w.Code, w.Body.String())
|
|
}
|
|
|
|
vol, _ := app.getVolunteerByEmail("titania@example.com")
|
|
if vol == nil || vol.ParticipantID == nil {
|
|
t.Fatal("volunteer/participant not created")
|
|
}
|
|
p, _ := app.getParticipant(*vol.ParticipantID)
|
|
if p == nil {
|
|
t.Fatal("participant not found")
|
|
}
|
|
if p.PreferredName != "Titania" {
|
|
t.Errorf("participant preferred_name = %q, want %q (not ticket_name)", p.PreferredName, "Titania")
|
|
}
|
|
if p.TicketName != "Titania Fairweather" {
|
|
t.Errorf("participant.TicketName = %q, want %q", p.TicketName, "Titania Fairweather")
|
|
}
|
|
}
|
|
|
|
func TestConfirmEmailAssignsKioskCode(t *testing.T) {
|
|
app := testApp(t)
|
|
mux := testMux(app)
|
|
|
|
app.db.Exec(`INSERT OR REPLACE INTO config (key, value) VALUES ('shift_signups_open', 'true')`)
|
|
app.baseURL = "https://example.com"
|
|
|
|
participant, _ := app.createParticipant(Participant{PreferredName: "Titania", TicketName: "Titania Fairweather", Email: "titania@example.com"})
|
|
token := "abc123def456"
|
|
app.createVolunteer(Volunteer{
|
|
Name: "Titania",
|
|
PreferredName: "Titania",
|
|
Email: "titania@example.com",
|
|
ParticipantID: &participant.ID,
|
|
ConfirmationToken: &token,
|
|
})
|
|
|
|
w := httptest.NewRecorder()
|
|
mux.ServeHTTP(w, testRequest("POST", "/api/public/confirm", map[string]any{"token": token}))
|
|
if w.Code != 200 {
|
|
t.Fatalf("expected 200, got %d", w.Code)
|
|
}
|
|
result := parseJSON(t, w)
|
|
if result["status"] != "confirmed" {
|
|
t.Fatalf("expected confirmed, got %v", result["status"])
|
|
}
|
|
if result["kiosk_link"] == nil {
|
|
t.Error("expected kiosk_link in response when signups are open")
|
|
}
|
|
|
|
// Kiosk code should be on the volunteer record, not a stub ticket.
|
|
vol, _ := app.getVolunteerByEmail("titania@example.com")
|
|
if vol == nil || vol.KioskCode == nil {
|
|
t.Fatal("expected volunteer to have a kiosk_code")
|
|
}
|
|
// No stub ticket should have been created.
|
|
tickets, _ := app.listTickets(&participant.ID, "")
|
|
if len(tickets) != 0 {
|
|
t.Errorf("expected no stub tickets, got %d", len(tickets))
|
|
}
|
|
}
|
|
|
|
func TestToggleShiftSignups(t *testing.T) {
|
|
app := testApp(t)
|
|
mux := testMux(app)
|
|
admin := testAdminUser(t, app)
|
|
tok := testToken(t, app, admin)
|
|
|
|
w := httptest.NewRecorder()
|
|
mux.ServeHTTP(w, testAuthRequest("POST", "/api/settings/shift-signups", map[string]any{"open": true}, tok))
|
|
if w.Code != 200 {
|
|
t.Fatalf("expected 200, got %d: %s", w.Code, w.Body.String())
|
|
}
|
|
result := parseJSON(t, w)
|
|
if result["shift_signups_open"] != true {
|
|
t.Errorf("expected shift_signups_open true, got %v", result)
|
|
}
|
|
|
|
// Check config stored
|
|
var val string
|
|
app.db.QueryRow(`SELECT value FROM config WHERE key = 'shift_signups_open'`).Scan(&val)
|
|
if val != "true" {
|
|
t.Errorf("config not stored, got %q", val)
|
|
}
|
|
}
|