feat(backend): leaderboard tiers, N+1 fix, contributors, certificate approval, physical disable
- leaderboard: batch-fetch users (GetUsersByIDs), deterministic tie-break by user_id - leaderboard: tier system (champion/gold/silver/bronze) based on rank percentile - store: HasCompletedCurriculum requires cert.Status == "issued"; pending does not satisfy - certificate: issueCertificateIfEligible creates pending row; admin approves via handleAdminIssueCertificate - certificate: handleRequestPhysicalCertificate returns 410 Gone (feature disabled) - settings: contributors CRUD (loadContributors, validateContributors, handleAdminContributorAvatarUpload) - community: expose contributors in /api/community response - rate_limit: separate DevMode bypass from redis-nil check in allowWSHandshake - router: remove /api/search route; fix missing tab indent on /api/meta line - migration 0032: contributors app_setting with seed entry
This commit is contained in:
@@ -0,0 +1,50 @@
|
||||
package api_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// TestPendingCertDoesNotShortCircuitCurriculumCheck ensures that a pending
|
||||
// certificate does not satisfy HasCompletedCurriculum. Only an issued
|
||||
// (non-revoked) certificate should bypass the lesson-completion check.
|
||||
func TestPendingCertDoesNotShortCircuitCurriculumCheck(t *testing.T) {
|
||||
e := setup(t)
|
||||
token, uid := register(t, e, "CertPending", "cert-pending@test.dev")
|
||||
admin := login(t, e, bootstrapAdminEmail)
|
||||
|
||||
// Forcibly create a pending certificate in the DB (simulating a student
|
||||
// who triggered issueCertificateIfEligible via quiz submit).
|
||||
_, err := e.pool.Exec(context.Background(),
|
||||
`INSERT INTO certificates (user_id, full_name, status)
|
||||
VALUES ($1, 'CertPending', 'pending')
|
||||
ON CONFLICT DO NOTHING`, uid)
|
||||
if err != nil {
|
||||
t.Fatalf("insert pending cert: %v", err)
|
||||
}
|
||||
|
||||
// The student has NOT passed any lessons — eligible must be false.
|
||||
code, body := e.do(t, "GET", "/api/me/certificate", token, nil)
|
||||
if code != http.StatusOK {
|
||||
t.Fatalf("get cert: %d %v", code, body)
|
||||
}
|
||||
if body["eligible"] != false {
|
||||
t.Fatalf("pending cert must not make student eligible; got eligible=%v", body["eligible"])
|
||||
}
|
||||
|
||||
// Admin issues the certificate manually.
|
||||
code, _ = e.do(t, "POST", "/api/admin/users/"+itoa(uid)+"/certificate/issue", admin, nil)
|
||||
if code != http.StatusOK {
|
||||
t.Fatalf("admin issue: %d", code)
|
||||
}
|
||||
|
||||
// Physical order endpoint must return 410 Gone.
|
||||
code, _ = e.do(t, "POST", "/api/me/certificate/physical", token, map[string]any{
|
||||
"recipientName": "Test", "phone": "09123456789", "address": "addr",
|
||||
"city": "tehran", "postalCode": "1234567890",
|
||||
})
|
||||
if code != http.StatusGone {
|
||||
t.Fatalf("physical endpoint must be disabled (410), got %d", code)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user