2026-03-05 16:51:39 -06:00
|
|
|
package main
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"net/http"
|
|
|
|
|
"net/http/httptest"
|
|
|
|
|
"testing"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
func TestConfirmVolunteer(t *testing.T) {
|
|
|
|
|
app := testApp(t)
|
|
|
|
|
mux := testMux(app)
|
|
|
|
|
admin := testAdminUser(t, app)
|
|
|
|
|
tok := testToken(t, app, admin)
|
|
|
|
|
|
|
|
|
|
dept, _ := app.createDepartment(Department{Name: "Gate"})
|
|
|
|
|
deptID := dept.ID
|
2026-03-06 07:11:19 -06:00
|
|
|
p, _ := app.createParticipant(Participant{PreferredName: "Titania", Email: "titania@test.com", EmailConfirmed: true})
|
|
|
|
|
v, _ := app.createVolunteer(Volunteer{ParticipantID: p.ID, DepartmentID: &deptID})
|
2026-03-05 16:51:39 -06:00
|
|
|
|
|
|
|
|
w := httptest.NewRecorder()
|
|
|
|
|
mux.ServeHTTP(w, testAuthRequest("POST", "/api/volunteers/"+itoa(v.ID)+"/confirm", nil, tok))
|
|
|
|
|
if w.Code != http.StatusOK {
|
|
|
|
|
t.Fatalf("expected 200, got %d: %s", w.Code, w.Body.String())
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
result := parseJSON(t, w)
|
|
|
|
|
vol := result["confirmed"]
|
|
|
|
|
if vol != true {
|
|
|
|
|
t.Error("expected confirmed=true in response")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
got, _ := app.getVolunteer(v.ID)
|
|
|
|
|
if got == nil || !got.Confirmed {
|
|
|
|
|
t.Error("volunteer should be confirmed in DB")
|
|
|
|
|
}
|
|
|
|
|
if got.ConfirmedAt == nil {
|
|
|
|
|
t.Error("confirmed_at should be set")
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestConfirmVolunteerIdempotent(t *testing.T) {
|
|
|
|
|
app := testApp(t)
|
|
|
|
|
mux := testMux(app)
|
|
|
|
|
admin := testAdminUser(t, app)
|
|
|
|
|
tok := testToken(t, app, admin)
|
|
|
|
|
|
2026-03-06 07:11:19 -06:00
|
|
|
p, _ := app.createParticipant(Participant{PreferredName: "Puck", Email: "puck@test.com", EmailConfirmed: true})
|
|
|
|
|
v, _ := app.createVolunteer(Volunteer{ParticipantID: p.ID})
|
2026-03-05 16:51:39 -06:00
|
|
|
|
|
|
|
|
// Confirm twice — second should be a no-op, not an error.
|
|
|
|
|
w := httptest.NewRecorder()
|
|
|
|
|
mux.ServeHTTP(w, testAuthRequest("POST", "/api/volunteers/"+itoa(v.ID)+"/confirm", nil, tok))
|
|
|
|
|
if w.Code != 200 {
|
|
|
|
|
t.Fatalf("first confirm: %d", w.Code)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
w = httptest.NewRecorder()
|
|
|
|
|
mux.ServeHTTP(w, testAuthRequest("POST", "/api/volunteers/"+itoa(v.ID)+"/confirm", nil, tok))
|
|
|
|
|
if w.Code != 200 {
|
|
|
|
|
t.Fatalf("second confirm: %d", w.Code)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestConfirmVolunteerRequiresRole(t *testing.T) {
|
|
|
|
|
app := testApp(t)
|
|
|
|
|
mux := testMux(app)
|
|
|
|
|
|
|
|
|
|
// Ticketing role should NOT be able to confirm volunteers.
|
|
|
|
|
ticketing := testUserWithRole(t, app, "ticket_lead", "ticketing", nil)
|
|
|
|
|
tok := testToken(t, app, ticketing)
|
|
|
|
|
|
2026-03-06 07:11:19 -06:00
|
|
|
p, _ := app.createParticipant(Participant{PreferredName: "Helena", EmailConfirmed: true})
|
|
|
|
|
v, _ := app.createVolunteer(Volunteer{ParticipantID: p.ID})
|
2026-03-05 16:51:39 -06:00
|
|
|
|
|
|
|
|
w := httptest.NewRecorder()
|
|
|
|
|
mux.ServeHTTP(w, testAuthRequest("POST", "/api/volunteers/"+itoa(v.ID)+"/confirm", nil, tok))
|
|
|
|
|
if w.Code != http.StatusForbidden {
|
|
|
|
|
t.Errorf("expected 403 for ticketing role, got %d", w.Code)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestUpdateVolunteerDepartment(t *testing.T) {
|
|
|
|
|
app := testApp(t)
|
|
|
|
|
mux := testMux(app)
|
|
|
|
|
admin := testAdminUser(t, app)
|
|
|
|
|
tok := testToken(t, app, admin)
|
|
|
|
|
|
|
|
|
|
dept, _ := app.createDepartment(Department{Name: "Gate"})
|
2026-03-06 07:11:19 -06:00
|
|
|
p, _ := app.createParticipant(Participant{PreferredName: "Hermia"})
|
|
|
|
|
v, _ := app.createVolunteer(Volunteer{ParticipantID: p.ID})
|
2026-03-05 16:51:39 -06:00
|
|
|
|
|
|
|
|
// Assign department via update.
|
|
|
|
|
w := httptest.NewRecorder()
|
|
|
|
|
mux.ServeHTTP(w, testAuthRequest("PUT", "/api/volunteers/"+itoa(v.ID), map[string]any{
|
2026-03-06 07:11:19 -06:00
|
|
|
"department_id": dept.ID,
|
2026-03-05 16:51:39 -06:00
|
|
|
}, tok))
|
|
|
|
|
if w.Code != 200 {
|
|
|
|
|
t.Fatalf("expected 200, got %d: %s", w.Code, w.Body.String())
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
got, _ := app.getVolunteer(v.ID)
|
|
|
|
|
if got.DepartmentID == nil || *got.DepartmentID != dept.ID {
|
|
|
|
|
t.Errorf("department_id = %v, want %d", got.DepartmentID, dept.ID)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestUpdateVolunteerCoLeadAutoConfirms(t *testing.T) {
|
|
|
|
|
app := testApp(t)
|
|
|
|
|
mux := testMux(app)
|
|
|
|
|
admin := testAdminUser(t, app)
|
|
|
|
|
tok := testToken(t, app, admin)
|
|
|
|
|
|
|
|
|
|
dept, _ := app.createDepartment(Department{Name: "Build"})
|
|
|
|
|
deptID := dept.ID
|
2026-03-06 07:11:19 -06:00
|
|
|
p, _ := app.createParticipant(Participant{PreferredName: "Lysander", Email: "lys@test.com", EmailConfirmed: true})
|
|
|
|
|
v, _ := app.createVolunteer(Volunteer{ParticipantID: p.ID, DepartmentID: &deptID})
|
2026-03-05 16:51:39 -06:00
|
|
|
|
|
|
|
|
// Verify not confirmed before update.
|
|
|
|
|
got, _ := app.getVolunteer(v.ID)
|
|
|
|
|
if got.Confirmed {
|
|
|
|
|
t.Fatal("should not be confirmed before update")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Update is_lead=true should auto-confirm.
|
|
|
|
|
w := httptest.NewRecorder()
|
|
|
|
|
mux.ServeHTTP(w, testAuthRequest("PUT", "/api/volunteers/"+itoa(v.ID), map[string]any{
|
2026-03-06 07:11:19 -06:00
|
|
|
"department_id": deptID, "is_lead": true,
|
2026-03-05 16:51:39 -06:00
|
|
|
}, tok))
|
|
|
|
|
if w.Code != 200 {
|
|
|
|
|
t.Fatalf("expected 200, got %d: %s", w.Code, w.Body.String())
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
got, _ = app.getVolunteer(v.ID)
|
|
|
|
|
if !got.IsLead {
|
|
|
|
|
t.Error("expected is_lead=true")
|
|
|
|
|
}
|
|
|
|
|
if !got.Confirmed {
|
|
|
|
|
t.Error("co-lead should be auto-confirmed")
|
|
|
|
|
}
|
|
|
|
|
}
|