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 v, _ := app.createVolunteer(Volunteer{ Name: "Titania", Email: "titania@test.com", DepartmentID: &deptID, EmailConfirmed: true, }) 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) v, _ := app.createVolunteer(Volunteer{Name: "Puck", Email: "puck@test.com", EmailConfirmed: true}) // 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) v, _ := app.createVolunteer(Volunteer{Name: "Helena", EmailConfirmed: true}) 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"}) v, _ := app.createVolunteer(Volunteer{Name: "Hermia"}) // Assign department via update. w := httptest.NewRecorder() mux.ServeHTTP(w, testAuthRequest("PUT", "/api/volunteers/"+itoa(v.ID), map[string]any{ "name": "Hermia", "department_id": dept.ID, }, 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 v, _ := app.createVolunteer(Volunteer{ Name: "Lysander", Email: "lys@test.com", DepartmentID: &deptID, EmailConfirmed: true, }) // 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{ "name": "Lysander", "department_id": deptID, "is_lead": true, }, 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") } }