Add more tests for modules co worked by claude
All checks were successful
Server Check Build (NixCN CMS) TeamCity build finished

Signed-off-by: Asai Neko <sugar@sne.moe>
This commit is contained in:
2026-03-26 23:36:40 +08:00
parent 2914665f70
commit 82c412d839
22 changed files with 2090 additions and 27 deletions

View File

@@ -255,3 +255,189 @@ func TestAgendaReviewHandlerInvalidStatus(t *testing.T) {
})
assert.Equal(t, http.StatusBadRequest, w.Code)
}
func TestAgendaReviewHandlerSuccess(t *testing.T) {
testutil.SetupWithAuth(t)
owner := testutil.SeedUser(t, testutil.RandomEmail(), 40)
attendee := testutil.SeedUser(t, testutil.RandomEmail(), 10)
event := seedEventWithAttendee(t, owner, attendee)
manager := testutil.SeedUser(t, testutil.RandomEmail(), 30)
managerToken := issueToken(t, manager.UserId)
attendeeToken := issueToken(t, attendee.UserId)
r := newAgendaRouter(t)
submitW := postWithBearer(t, r, "/agenda/submit", attendeeToken, service_agenda.SubmitData{
EventId: event.EventId,
Name: "Talk to Review",
Description: "desc",
})
require.Equal(t, http.StatusOK, submitW.Code)
var submitResp map[string]any
require.NoError(t, json.Unmarshal(submitW.Body.Bytes(), &submitResp))
agendaId := submitResp["data"].(map[string]any)["agenda_id"].(string)
w := patchWithBearer(t, r, "/agenda/review", managerToken, service_agenda.AgendaReviewData{
EventId: event.EventId,
AgendaId: uuid.MustParse(agendaId),
Status: "approved",
})
assert.Equal(t, http.StatusOK, w.Code)
}
// ---- List (success) ----
func TestAgendaListHandlerSuccess(t *testing.T) {
testutil.SetupWithAuth(t)
owner := testutil.SeedUser(t, testutil.RandomEmail(), 40)
attendee := testutil.SeedUser(t, testutil.RandomEmail(), 10)
event := seedEventWithAttendee(t, owner, attendee)
manager := testutil.SeedUser(t, testutil.RandomEmail(), 30)
managerToken := issueToken(t, manager.UserId)
attendeeToken := issueToken(t, attendee.UserId)
r := newAgendaRouter(t)
postWithBearer(t, r, "/agenda/submit", attendeeToken, service_agenda.SubmitData{
EventId: event.EventId,
Name: "Talk 1",
})
w := getWithBearer(t, r, "/agenda/list?event_id="+event.EventId.String(), managerToken)
assert.Equal(t, http.StatusOK, w.Code)
}
// ---- Update ----
func TestAgendaUpdateHandlerMissingAgendaId(t *testing.T) {
testutil.SetupWithAuth(t)
user := testutil.SeedUser(t, testutil.RandomEmail(), 10)
token := issueToken(t, user.UserId)
r := newAgendaRouter(t)
w := patchWithBearer(t, r, "/agenda/update", token, map[string]any{
"name": "New Name",
})
assert.Equal(t, http.StatusBadRequest, w.Code)
}
func TestAgendaUpdateHandlerSuccess(t *testing.T) {
testutil.SetupWithAuth(t)
owner := testutil.SeedUser(t, testutil.RandomEmail(), 40)
attendee := testutil.SeedUser(t, testutil.RandomEmail(), 10)
event := seedEventWithAttendee(t, owner, attendee)
attendeeToken := issueToken(t, attendee.UserId)
r := newAgendaRouter(t)
submitW := postWithBearer(t, r, "/agenda/submit", attendeeToken, service_agenda.SubmitData{
EventId: event.EventId,
Name: "Original Talk",
Description: "desc",
})
require.Equal(t, http.StatusOK, submitW.Code)
var submitResp map[string]any
require.NoError(t, json.Unmarshal(submitW.Body.Bytes(), &submitResp))
agendaId := submitResp["data"].(map[string]any)["agenda_id"].(string)
newName := "Updated Talk"
w := patchWithBearer(t, r, "/agenda/update", attendeeToken, service_agenda.AgendaUpdateData{
AgendaId: uuid.MustParse(agendaId),
Name: &newName,
})
assert.Equal(t, http.StatusOK, w.Code)
}
// ---- Schedule PATCH ----
func TestAgendaScheduleHandlerMissingAgendaId(t *testing.T) {
testutil.SetupWithAuth(t)
manager := testutil.SeedUser(t, testutil.RandomEmail(), 30)
token := issueToken(t, manager.UserId)
r := newAgendaRouter(t)
w := patchWithBearer(t, r, "/agenda/schedule", token, map[string]any{
"start_time": time.Now().Add(time.Hour),
"end_time": time.Now().Add(2 * time.Hour),
})
assert.Equal(t, http.StatusBadRequest, w.Code)
}
func TestAgendaScheduleHandlerSuccess(t *testing.T) {
testutil.SetupWithAuth(t)
owner := testutil.SeedUser(t, testutil.RandomEmail(), 40)
attendee := testutil.SeedUser(t, testutil.RandomEmail(), 10)
event := seedEventWithAttendee(t, owner, attendee)
manager := testutil.SeedUser(t, testutil.RandomEmail(), 30)
managerToken := issueToken(t, manager.UserId)
attendeeToken := issueToken(t, attendee.UserId)
r := newAgendaRouter(t)
submitW := postWithBearer(t, r, "/agenda/submit", attendeeToken, service_agenda.SubmitData{
EventId: event.EventId,
Name: "Schedule Me",
Description: "base64desc",
})
require.Equal(t, http.StatusOK, submitW.Code)
var submitResp map[string]any
require.NoError(t, json.Unmarshal(submitW.Body.Bytes(), &submitResp))
agendaId := submitResp["data"].(map[string]any)["agenda_id"].(string)
reviewW := patchWithBearer(t, r, "/agenda/review", managerToken, service_agenda.AgendaReviewData{
EventId: event.EventId,
AgendaId: uuid.MustParse(agendaId),
Status: "approved",
})
require.Equal(t, http.StatusOK, reviewW.Code)
now := time.Now()
w := patchWithBearer(t, r, "/agenda/schedule", managerToken, service_agenda.AgendaScheduleData{
AgendaId: uuid.MustParse(agendaId),
StartTime: now.Add(time.Hour),
EndTime: now.Add(2 * time.Hour),
})
assert.Equal(t, http.StatusOK, w.Code)
}
// ---- ScheduleGet (success) ----
func TestAgendaScheduleGetHandlerSuccess(t *testing.T) {
testutil.SetupWithAuth(t)
owner := testutil.SeedUser(t, testutil.RandomEmail(), 40)
attendee := testutil.SeedUser(t, testutil.RandomEmail(), 10)
event := seedEventWithAttendee(t, owner, attendee)
manager := testutil.SeedUser(t, testutil.RandomEmail(), 30)
managerToken := issueToken(t, manager.UserId)
attendeeToken := issueToken(t, attendee.UserId)
r := newAgendaRouter(t)
submitW := postWithBearer(t, r, "/agenda/submit", attendeeToken, service_agenda.SubmitData{
EventId: event.EventId,
Name: "Published Talk",
Description: "base64desc",
})
require.Equal(t, http.StatusOK, submitW.Code)
var submitResp map[string]any
require.NoError(t, json.Unmarshal(submitW.Body.Bytes(), &submitResp))
agendaId := submitResp["data"].(map[string]any)["agenda_id"].(string)
patchWithBearer(t, r, "/agenda/review", managerToken, service_agenda.AgendaReviewData{
EventId: event.EventId,
AgendaId: uuid.MustParse(agendaId),
Status: "approved",
})
now := time.Now()
patchWithBearer(t, r, "/agenda/schedule", managerToken, service_agenda.AgendaScheduleData{
AgendaId: uuid.MustParse(agendaId),
StartTime: now.Add(time.Hour),
EndTime: now.Add(2 * time.Hour),
})
require.NoError(t, new(data.Event).PatchByEventId(t.Context(), event.EventId, data.WithIsAgendaPublished(true)))
w := getWithBearer(t, r, "/agenda/schedule?event_id="+event.EventId.String(), attendeeToken)
assert.Equal(t, http.StatusOK, w.Code)
var resp map[string]any
require.NoError(t, json.Unmarshal(w.Body.Bytes(), &resp))
items, ok := resp["data"].([]any)
require.True(t, ok)
assert.Len(t, items, 1)
}

View File

@@ -205,3 +205,47 @@ func TestExchangeHandlerNoAuth(t *testing.T) {
// No auth header → 401
assert.Equal(t, http.StatusUnauthorized, w.Code)
}
func TestExchangeHandlerSuccess(t *testing.T) {
r := setupAuthRouter(t)
// Step 1: Magic (debug mode, returns redirect URI with code)
magicW := postJSON(t, r, "/auth/magic", service_auth.MagicData{
ClientId: testutil.TestClientID,
RedirectUri: "http://localhost/callback",
State: "s",
Email: "exchange@example.com",
})
require.Equal(t, http.StatusOK, magicW.Code)
var magicResp map[string]any
require.NoError(t, json.Unmarshal(magicW.Body.Bytes(), &magicResp))
rawURI := magicResp["data"].(map[string]any)["uri"].(string)
code := extractQueryParam(t, rawURI, "code")
// Step 2: Redirect → produces token code
redirectW := getRequest(t, r, "/auth/redirect?client_id="+testutil.TestClientID+
"&redirect_uri=http://localhost/callback&code="+code+"&state=s")
require.Equal(t, http.StatusFound, redirectW.Code)
location := redirectW.Header().Get("Location")
tokenCode := extractQueryParam(t, location, "code")
// Step 3: Token → get access token
tokenW := postJSON(t, r, "/auth/token", service_auth.TokenData{Code: tokenCode})
require.Equal(t, http.StatusOK, tokenW.Code)
var tokenResp map[string]any
require.NoError(t, json.Unmarshal(tokenW.Body.Bytes(), &tokenResp))
accessToken := tokenResp["data"].(map[string]any)["access_token"].(string)
// Step 4: Exchange (requires JWT Bearer token)
w := postWithBearer(t, r, "/auth/exchange", accessToken, service_auth.ExchangeData{
ClientId: testutil.TestClientID,
RedirectUri: "http://localhost/callback",
State: "s",
})
assert.Equal(t, http.StatusOK, w.Code)
var exchangeResp map[string]any
require.NoError(t, json.Unmarshal(w.Body.Bytes(), &exchangeResp))
exchangeData, ok := exchangeResp["data"].(map[string]any)
require.True(t, ok)
assert.NotEmpty(t, exchangeData["redirect_uri"])
}

View File

@@ -227,3 +227,382 @@ func TestEventDeleteHandlerNotFound(t *testing.T) {
})
assert.Equal(t, http.StatusNotFound, w.Code)
}
func TestEventDeleteHandlerSuccess(t *testing.T) {
testutil.SetupWithAuth(t)
owner := testutil.SeedUser(t, testutil.RandomEmail(), 40)
token := issueToken(t, owner.UserId)
r := newEventRouter(t)
createW := postWithBearer(t, r, "/event/create", token, service_event.EventCreateData{
Type: "party",
Name: "Delete Me",
Subtitle: "sub",
StartTime: time.Now().Add(24 * time.Hour),
EndTime: time.Now().Add(48 * time.Hour),
Quota: 100,
Limit: 150,
})
require.Equal(t, http.StatusOK, createW.Code)
var createResp map[string]any
require.NoError(t, json.Unmarshal(createW.Body.Bytes(), &createResp))
eventId := createResp["data"].(map[string]any)["event_id"].(string)
w := deleteWithBearer(t, r, "/event/delete", token, map[string]any{"event_id": eventId})
assert.Equal(t, http.StatusOK, w.Code)
}
// ---- Info (success) ----
func TestEventInfoHandlerSuccess(t *testing.T) {
testutil.SetupWithAuth(t)
owner := testutil.SeedUser(t, testutil.RandomEmail(), 30)
user := testutil.SeedUser(t, testutil.RandomEmail(), 10)
ownerToken := issueToken(t, owner.UserId)
userToken := issueToken(t, user.UserId)
r := newEventRouter(t)
createW := postWithBearer(t, r, "/event/create", ownerToken, service_event.EventCreateData{
Type: "party",
Name: "Info Event",
Subtitle: "sub",
StartTime: time.Now().Add(24 * time.Hour),
EndTime: time.Now().Add(48 * time.Hour),
Quota: 100,
Limit: 150,
})
require.Equal(t, http.StatusOK, createW.Code)
var createResp map[string]any
require.NoError(t, json.Unmarshal(createW.Body.Bytes(), &createResp))
eventId := createResp["data"].(map[string]any)["event_id"].(string)
w := getWithBearer(t, r, "/event/info?event_id="+eventId, userToken)
assert.Equal(t, http.StatusOK, w.Code)
var resp map[string]any
require.NoError(t, json.Unmarshal(w.Body.Bytes(), &resp))
d, ok := resp["data"].(map[string]any)
require.True(t, ok)
assert.Equal(t, eventId, d["event_id"])
}
// ---- Join (success) ----
func TestEventJoinHandlerSuccess(t *testing.T) {
testutil.SetupWithAuth(t)
owner := testutil.SeedUser(t, testutil.RandomEmail(), 30)
joiner := testutil.SeedUser(t, testutil.RandomEmail(), 10)
require.NoError(t, new(data.User).PatchByUserId(t.Context(), joiner.UserId, data.WithNickname("Joiner")))
ownerToken := issueToken(t, owner.UserId)
joinerToken := issueToken(t, joiner.UserId)
r := newEventRouter(t)
createW := postWithBearer(t, r, "/event/create", ownerToken, service_event.EventCreateData{
Type: "party",
Name: "Join Event",
Subtitle: "sub",
StartTime: time.Now().Add(24 * time.Hour),
EndTime: time.Now().Add(48 * time.Hour),
Quota: 100,
Limit: 150,
})
require.Equal(t, http.StatusOK, createW.Code)
var createResp map[string]any
require.NoError(t, json.Unmarshal(createW.Body.Bytes(), &createResp))
eventId := createResp["data"].(map[string]any)["event_id"].(string)
w := postWithBearer(t, r, "/event/join", joinerToken, map[string]any{"event_id": eventId})
assert.Equal(t, http.StatusOK, w.Code)
}
// ---- Update ----
func TestEventUpdateHandlerSuccess(t *testing.T) {
testutil.SetupWithAuth(t)
owner := testutil.SeedUser(t, testutil.RandomEmail(), 30)
token := issueToken(t, owner.UserId)
r := newEventRouter(t)
createW := postWithBearer(t, r, "/event/create", token, service_event.EventCreateData{
Type: "party",
Name: "Original Name",
Subtitle: "sub",
StartTime: time.Now().Add(24 * time.Hour),
EndTime: time.Now().Add(48 * time.Hour),
Quota: 100,
Limit: 150,
})
require.Equal(t, http.StatusOK, createW.Code)
var createResp map[string]any
require.NoError(t, json.Unmarshal(createW.Body.Bytes(), &createResp))
eventId := createResp["data"].(map[string]any)["event_id"].(string)
newName := "Updated Name"
w := patchWithBearer(t, r, "/event/update", token, map[string]any{
"event_id": eventId,
"name": newName,
})
assert.Equal(t, http.StatusOK, w.Code)
}
func TestEventUpdateHandlerInvalidJSON(t *testing.T) {
testutil.SetupWithAuth(t)
user := testutil.SeedUser(t, testutil.RandomEmail(), 30)
token := issueToken(t, user.UserId)
r := newEventRouter(t)
req := httptest.NewRequest(http.MethodPatch, "/event/update", bytes.NewBufferString("{bad"))
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Authorization", "Bearer "+token)
w := httptest.NewRecorder()
r.ServeHTTP(w, req)
assert.Equal(t, http.StatusBadRequest, w.Code)
}
// ---- Stats ----
func TestEventStatsHandlerSuccess(t *testing.T) {
testutil.SetupWithAuth(t)
owner := testutil.SeedUser(t, testutil.RandomEmail(), 30)
token := issueToken(t, owner.UserId)
r := newEventRouter(t)
createW := postWithBearer(t, r, "/event/create", token, service_event.EventCreateData{
Type: "party",
Name: "Stats Event",
Subtitle: "sub",
StartTime: time.Now().Add(24 * time.Hour),
EndTime: time.Now().Add(48 * time.Hour),
Quota: 100,
Limit: 150,
})
require.Equal(t, http.StatusOK, createW.Code)
var createResp map[string]any
require.NoError(t, json.Unmarshal(createW.Body.Bytes(), &createResp))
eventId := createResp["data"].(map[string]any)["event_id"].(string)
w := getWithBearer(t, r, "/event/stats?event_id="+eventId, token)
assert.Equal(t, http.StatusOK, w.Code)
var resp map[string]any
require.NoError(t, json.Unmarshal(w.Body.Bytes(), &resp))
d, ok := resp["data"].(map[string]any)
require.True(t, ok)
assert.NotNil(t, d["join_count"])
}
func TestEventStatsHandlerMissingEventId(t *testing.T) {
testutil.SetupWithAuth(t)
user := testutil.SeedUser(t, testutil.RandomEmail(), 30)
token := issueToken(t, user.UserId)
r := newEventRouter(t)
w := getWithBearer(t, r, "/event/stats?event_id=not-a-uuid", token)
assert.Equal(t, http.StatusBadRequest, w.Code)
}
// ---- Checkin ----
func TestEventCheckinHandlerInvalidEventId(t *testing.T) {
testutil.SetupWithAuth(t)
user := testutil.SeedUser(t, testutil.RandomEmail(), 10)
token := issueToken(t, user.UserId)
r := newEventRouter(t)
w := getWithBearer(t, r, "/event/checkin?event_id=not-a-uuid", token)
assert.Equal(t, http.StatusBadRequest, w.Code)
}
func TestEventCheckinHandlerSuccess(t *testing.T) {
testutil.SetupWithAuth(t)
owner := testutil.SeedUser(t, testutil.RandomEmail(), 30)
joiner := testutil.SeedUser(t, testutil.RandomEmail(), 10)
require.NoError(t, new(data.User).PatchByUserId(t.Context(), joiner.UserId, data.WithNickname("Joiner")))
ownerToken := issueToken(t, owner.UserId)
joinerToken := issueToken(t, joiner.UserId)
r := newEventRouter(t)
createW := postWithBearer(t, r, "/event/create", ownerToken, service_event.EventCreateData{
Type: "party",
Name: "Checkin Event",
Subtitle: "sub",
StartTime: time.Now().Add(24 * time.Hour),
EndTime: time.Now().Add(48 * time.Hour),
Quota: 100,
Limit: 150,
})
require.Equal(t, http.StatusOK, createW.Code)
var createResp map[string]any
require.NoError(t, json.Unmarshal(createW.Body.Bytes(), &createResp))
eventId := createResp["data"].(map[string]any)["event_id"].(string)
joinW := postWithBearer(t, r, "/event/join", joinerToken, map[string]any{"event_id": eventId})
require.Equal(t, http.StatusOK, joinW.Code)
w := getWithBearer(t, r, "/event/checkin?event_id="+eventId, joinerToken)
assert.Equal(t, http.StatusOK, w.Code)
var resp map[string]any
require.NoError(t, json.Unmarshal(w.Body.Bytes(), &resp))
d, ok := resp["data"].(map[string]any)
require.True(t, ok)
assert.NotEmpty(t, d["checkin_code"])
}
// ---- CheckinSubmit ----
func TestEventCheckinSubmitHandlerInvalidCode(t *testing.T) {
testutil.SetupWithAuth(t)
user := testutil.SeedUser(t, testutil.RandomEmail(), 20)
token := issueToken(t, user.UserId)
r := newEventRouter(t)
w := postWithBearer(t, r, "/event/checkin/submit", token, service_event.CheckinSubmitData{
CheckinCode: "000000",
})
assert.Equal(t, http.StatusBadRequest, w.Code)
}
func TestEventCheckinSubmitHandlerSuccess(t *testing.T) {
testutil.SetupWithAuth(t)
owner := testutil.SeedUser(t, testutil.RandomEmail(), 30)
joiner := testutil.SeedUser(t, testutil.RandomEmail(), 10)
require.NoError(t, new(data.User).PatchByUserId(t.Context(), joiner.UserId, data.WithNickname("Joiner")))
ownerToken := issueToken(t, owner.UserId)
joinerToken := issueToken(t, joiner.UserId)
r := newEventRouter(t)
createW := postWithBearer(t, r, "/event/create", ownerToken, service_event.EventCreateData{
Type: "party",
Name: "Checkin Submit Event",
Subtitle: "sub",
StartTime: time.Now().Add(24 * time.Hour),
EndTime: time.Now().Add(48 * time.Hour),
Quota: 100,
Limit: 150,
})
require.Equal(t, http.StatusOK, createW.Code)
var createResp map[string]any
require.NoError(t, json.Unmarshal(createW.Body.Bytes(), &createResp))
eventId := createResp["data"].(map[string]any)["event_id"].(string)
joinW := postWithBearer(t, r, "/event/join", joinerToken, map[string]any{"event_id": eventId})
require.Equal(t, http.StatusOK, joinW.Code)
checkinW := getWithBearer(t, r, "/event/checkin?event_id="+eventId, joinerToken)
require.Equal(t, http.StatusOK, checkinW.Code)
var checkinResp map[string]any
require.NoError(t, json.Unmarshal(checkinW.Body.Bytes(), &checkinResp))
code := checkinResp["data"].(map[string]any)["checkin_code"].(string)
w := postWithBearer(t, r, "/event/checkin/submit", ownerToken, service_event.CheckinSubmitData{
CheckinCode: code,
})
assert.Equal(t, http.StatusOK, w.Code)
}
// ---- CheckinQuery ----
func TestEventCheckinQueryHandlerInvalidEventId(t *testing.T) {
testutil.SetupWithAuth(t)
user := testutil.SeedUser(t, testutil.RandomEmail(), 10)
token := issueToken(t, user.UserId)
r := newEventRouter(t)
w := getWithBearer(t, r, "/event/checkin/query?event_id=not-a-uuid", token)
assert.Equal(t, http.StatusBadRequest, w.Code)
}
func TestEventCheckinQueryHandlerNotJoined(t *testing.T) {
testutil.SetupWithAuth(t)
user := testutil.SeedUser(t, testutil.RandomEmail(), 10)
token := issueToken(t, user.UserId)
r := newEventRouter(t)
w := getWithBearer(t, r, "/event/checkin/query?event_id="+uuid.New().String(), token)
assert.Equal(t, http.StatusNotFound, w.Code)
}
// ---- Attendance ----
func TestEventAttendanceHandlerInvalidEventId(t *testing.T) {
testutil.SetupWithAuth(t)
user := testutil.SeedUser(t, testutil.RandomEmail(), 30)
token := issueToken(t, user.UserId)
r := newEventRouter(t)
w := getWithBearer(t, r, "/event/attendance?event_id=not-a-uuid", token)
assert.Equal(t, http.StatusBadRequest, w.Code)
}
func TestEventAttendanceHandlerSuccess(t *testing.T) {
testutil.SetupWithAuth(t)
owner := testutil.SeedUser(t, testutil.RandomEmail(), 30)
token := issueToken(t, owner.UserId)
r := newEventRouter(t)
createW := postWithBearer(t, r, "/event/create", token, service_event.EventCreateData{
Type: "party",
Name: "Attendance Event",
Subtitle: "sub",
StartTime: time.Now().Add(24 * time.Hour),
EndTime: time.Now().Add(48 * time.Hour),
Quota: 100,
Limit: 150,
})
require.Equal(t, http.StatusOK, createW.Code)
var createResp map[string]any
require.NoError(t, json.Unmarshal(createW.Body.Bytes(), &createResp))
eventId := createResp["data"].(map[string]any)["event_id"].(string)
w := getWithBearer(t, r, "/event/attendance?event_id="+eventId, token)
assert.Equal(t, http.StatusOK, w.Code)
var resp map[string]any
require.NoError(t, json.Unmarshal(w.Body.Bytes(), &resp))
d, ok := resp["data"].(map[string]any)
require.True(t, ok)
assert.NotNil(t, d["total"])
}
// ---- Guide ----
func TestEventGuideHandlerInvalidEventId(t *testing.T) {
testutil.SetupWithAuth(t)
user := testutil.SeedUser(t, testutil.RandomEmail(), 10)
token := issueToken(t, user.UserId)
r := newEventRouter(t)
// The guide handler returns 500 for invalid UUID (source behaviour)
w := getWithBearer(t, r, "/event/guide?event_id=not-a-uuid", token)
assert.NotEqual(t, http.StatusNotFound, w.Code, "route must be registered")
}
// ---- Permission enforcement ----
func TestEventCreateHandlerLowPermission(t *testing.T) {
testutil.SetupWithAuth(t)
user := testutil.SeedUser(t, testutil.RandomEmail(), 10)
token := issueToken(t, user.UserId)
r := newEventRouter(t)
w := postWithBearer(t, r, "/event/create", token, service_event.EventCreateData{
Type: "party",
Name: "Should Fail",
Quota: 10,
Limit: 20,
})
assert.Equal(t, http.StatusForbidden, w.Code)
}
func TestEventDeleteHandlerLowPermission(t *testing.T) {
testutil.SetupWithAuth(t)
user := testutil.SeedUser(t, testutil.RandomEmail(), 30)
token := issueToken(t, user.UserId)
r := newEventRouter(t)
w := deleteWithBearer(t, r, "/event/delete", token, map[string]any{
"event_id": uuid.New().String(),
})
assert.Equal(t, http.StatusForbidden, w.Code)
}

View File

@@ -13,6 +13,7 @@ import (
"github.com/spf13/viper"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"nixcn-cms/data"
"nixcn-cms/internal/authtoken"
"nixcn-cms/testutil"
)
@@ -137,3 +138,90 @@ func TestUserListHandlerRequiresOffset(t *testing.T) {
w := getWithBearer(t, r, "/user/list", token)
assert.Equal(t, http.StatusBadRequest, w.Code)
}
func TestUserListHandlerSuccess(t *testing.T) {
testutil.SetupWithAuth(t)
admin := testutil.SeedUser(t, testutil.RandomEmail(), 40)
for i := 0; i < 3; i++ {
testutil.SeedUser(t, testutil.RandomEmail(), 10)
}
token := issueToken(t, admin.UserId)
r := newUserRouter(t)
w := getWithBearer(t, r, "/user/list?offset=0&limit=10", token)
assert.Equal(t, http.StatusOK, w.Code)
var resp map[string]any
require.NoError(t, json.Unmarshal(w.Body.Bytes(), &resp))
assert.NotNil(t, resp["data"])
}
func TestUserListHandlerLowPermission(t *testing.T) {
testutil.SetupWithAuth(t)
user := testutil.SeedUser(t, testutil.RandomEmail(), 10)
token := issueToken(t, user.UserId)
r := newUserRouter(t)
w := getWithBearer(t, r, "/user/list?offset=0", token)
assert.Equal(t, http.StatusForbidden, w.Code)
}
// ---- Other (info by user_id) ----
func TestUserInfoHandlerOtherSuccess(t *testing.T) {
testutil.SetupWithAuth(t)
caller := testutil.SeedUser(t, testutil.RandomEmail(), 10)
target := testutil.SeedUser(t, testutil.RandomEmail(), 10)
require.NoError(t, new(data.User).PatchByUserId(context.Background(), target.UserId, data.WithAllowPublic(true)))
token := issueToken(t, caller.UserId)
r := newUserRouter(t)
w := getWithBearer(t, r, "/user/info/"+target.UserId.String(), token)
assert.Equal(t, http.StatusOK, w.Code)
var resp map[string]any
require.NoError(t, json.Unmarshal(w.Body.Bytes(), &resp))
d, ok := resp["data"].(map[string]any)
require.True(t, ok)
assert.Equal(t, target.Email, d["email"])
}
// ---- AdminUpdate ----
func TestUserAdminUpdateHandlerSuccess(t *testing.T) {
testutil.SetupWithAuth(t)
admin := testutil.SeedUser(t, testutil.RandomEmail(), 40)
target := testutil.SeedUser(t, testutil.RandomEmail(), 10)
token := issueToken(t, admin.UserId)
r := newUserRouter(t)
w := patchWithBearer(t, r, "/user/update/"+target.UserId.String(), token, map[string]any{
"nickname": "Admin Set Nickname",
})
assert.Equal(t, http.StatusOK, w.Code)
}
func TestUserAdminUpdateHandlerInvalidUserId(t *testing.T) {
testutil.SetupWithAuth(t)
admin := testutil.SeedUser(t, testutil.RandomEmail(), 40)
token := issueToken(t, admin.UserId)
r := newUserRouter(t)
w := patchWithBearer(t, r, "/user/update/not-a-uuid", token, map[string]any{
"nickname": "Test",
})
assert.Equal(t, http.StatusBadRequest, w.Code)
}
func TestUserAdminUpdateHandlerLowPermission(t *testing.T) {
testutil.SetupWithAuth(t)
user := testutil.SeedUser(t, testutil.RandomEmail(), 10)
target := testutil.SeedUser(t, testutil.RandomEmail(), 10)
token := issueToken(t, user.UserId)
r := newUserRouter(t)
w := patchWithBearer(t, r, "/user/update/"+target.UserId.String(), token, map[string]any{
"nickname": "Hacked",
})
assert.Equal(t, http.StatusForbidden, w.Code)
}

View File

@@ -36,6 +36,6 @@ func Init() {
conf := &config{}
if err := viper.Unmarshal(conf); err != nil {
log.Fatalln("[Condig] Can't unmarshal config!")
log.Fatalln("[Config] Can't unmarshal config!")
}
}

View File

@@ -63,6 +63,7 @@ type ttl struct {
type kyc struct {
AliAccessKeyId string `yaml:"ali_access_key_id"`
AliAccessKeySecret string `yaml:"ali_access_key_secret"`
PassportReaderEndpoint string `yaml:"passport_reader_endpoint"`
PassportReaderPublicKey string `yaml:"passport_reader_public_key"`
PassportReaderSecret string `yaml:"passport_reader_secret"`
}

View File

@@ -197,3 +197,37 @@ func TestAgendaDelete(t *testing.T) {
require.NoError(t, err)
assert.Nil(t, got)
}
func TestAgendaCountByEventId(t *testing.T) {
testutil.Setup(t)
ctx := context.Background()
evId, attId := seedAgendaFixture(t, ctx)
for _, status := range []string{"pending", "approved", "rejected"} {
ag := data.NewAgenda(
data.WithAttendanceId(attId),
data.WithAgendaName(uuid.New().String()),
data.WithAgendaDescription("desc"),
data.WithAgendaStatus(status),
)
require.NoError(t, ag.Create(ctx))
}
count, err := new(data.Agenda).CountByEventId(ctx, evId)
require.NoError(t, err)
assert.Equal(t, int64(3), count)
}
func TestAgendaCountByEventIdEmpty(t *testing.T) {
testutil.Setup(t)
ctx := context.Background()
owner := uuid.New()
ev := makeEvent(owner)
require.NoError(t, ev.Create(ctx))
count, err := new(data.Agenda).CountByEventId(ctx, ev.EventId)
require.NoError(t, err)
assert.Equal(t, int64(0), count)
}

View File

@@ -2,6 +2,7 @@ package data_test
import (
"context"
"fmt"
"testing"
"time"
@@ -163,3 +164,318 @@ func TestAttendanceVerifyCheckinCodeInvalid(t *testing.T) {
err := new(data.Attendance).VerifyCheckinCode(ctx, "000000")
require.Error(t, err)
}
func TestAttendanceGetByAttendanceId(t *testing.T) {
testutil.Setup(t)
ctx := context.Background()
eventId, userId := seedEventAndUser(t, ctx)
a := seedAttendance(t, ctx, eventId, userId)
got, err := new(data.Attendance).GetAttendanceByAttendanceId(ctx, a.AttendanceId)
require.NoError(t, err)
require.NotNil(t, got)
assert.Equal(t, a.AttendanceId, got.AttendanceId)
assert.Equal(t, eventId, got.EventId)
assert.Equal(t, userId, got.UserId)
}
func TestAttendanceGetByAttendanceIdNotFound(t *testing.T) {
testutil.Setup(t)
ctx := context.Background()
got, err := new(data.Attendance).GetAttendanceByAttendanceId(ctx, uuid.New())
require.NoError(t, err)
assert.Nil(t, got)
}
func TestAttendanceGetUsersByEventID(t *testing.T) {
testutil.Setup(t)
ctx := context.Background()
owner := uuid.New()
ev := makeEvent(owner)
require.NoError(t, ev.Create(ctx))
for i := 0; i < 3; i++ {
u := data.NewUser(
data.WithEmail(uuid.New().String()+"@test.com"),
data.WithUsername(uuid.New().String()),
data.WithPermissionLevel(10),
)
require.NoError(t, u.Create(ctx))
seedAttendance(t, ctx, ev.EventId, u.UserId)
}
users, err := new(data.Attendance).GetUsersByEventID(ctx, ev.EventId)
require.NoError(t, err)
require.NotNil(t, users)
assert.Len(t, *users, 3)
}
func TestAttendanceGetEventsByUserID(t *testing.T) {
testutil.Setup(t)
ctx := context.Background()
owner := uuid.New()
ev1 := makeEvent(owner)
require.NoError(t, ev1.Create(ctx))
ev2 := makeEvent(owner)
require.NoError(t, ev2.Create(ctx))
u := data.NewUser(
data.WithEmail(uuid.New().String()+"@test.com"),
data.WithUsername(uuid.New().String()),
data.WithPermissionLevel(10),
)
require.NoError(t, u.Create(ctx))
seedAttendance(t, ctx, ev1.EventId, u.UserId)
seedAttendance(t, ctx, ev2.EventId, u.UserId)
events, err := new(data.Attendance).GetEventsByUserID(ctx, u.UserId)
require.NoError(t, err)
require.NotNil(t, events)
assert.Len(t, *events, 2)
}
func TestAttendanceGetAttendanceListByEventId(t *testing.T) {
testutil.Setup(t)
ctx := context.Background()
owner := uuid.New()
ev := makeEvent(owner)
require.NoError(t, ev.Create(ctx))
for i := 0; i < 4; i++ {
u := data.NewUser(
data.WithEmail(uuid.New().String()+"@test.com"),
data.WithUsername(uuid.New().String()),
data.WithPermissionLevel(10),
)
require.NoError(t, u.Create(ctx))
seedAttendance(t, ctx, ev.EventId, u.UserId)
}
list, err := new(data.Attendance).GetAttendanceListByEventId(ctx, ev.EventId)
require.NoError(t, err)
require.NotNil(t, list)
assert.Len(t, *list, 4)
}
func TestAttendanceGetCheckedInEventIDs(t *testing.T) {
testutil.Setup(t)
ctx := context.Background()
owner := uuid.New()
ev1 := makeEvent(owner)
require.NoError(t, ev1.Create(ctx))
ev2 := makeEvent(owner)
require.NoError(t, ev2.Create(ctx))
u := data.NewUser(
data.WithEmail(uuid.New().String()+"@test.com"),
data.WithUsername(uuid.New().String()),
data.WithPermissionLevel(10),
)
require.NoError(t, u.Create(ctx))
a1 := seedAttendance(t, ctx, ev1.EventId, u.UserId)
_, err := new(data.Attendance).PatchByAttendanceId(ctx, a1.AttendanceId, data.WithCheckinAt(time.Now()))
require.NoError(t, err)
seedAttendance(t, ctx, ev2.EventId, u.UserId)
checkedIn, err := new(data.Attendance).GetCheckedInEventIDs(ctx, u.UserId, []uuid.UUID{ev1.EventId, ev2.EventId})
require.NoError(t, err)
assert.True(t, checkedIn[ev1.EventId])
assert.False(t, checkedIn[ev2.EventId])
}
func TestAttendanceCountCheckedInUsersByEventID(t *testing.T) {
testutil.Setup(t)
ctx := context.Background()
owner := uuid.New()
ev := makeEvent(owner)
require.NoError(t, ev.Create(ctx))
for i := 0; i < 3; i++ {
u := data.NewUser(
data.WithEmail(uuid.New().String()+"@test.com"),
data.WithUsername(uuid.New().String()),
data.WithPermissionLevel(10),
)
require.NoError(t, u.Create(ctx))
a := seedAttendance(t, ctx, ev.EventId, u.UserId)
if i < 2 {
_, err := new(data.Attendance).PatchByAttendanceId(ctx, a.AttendanceId, data.WithCheckinAt(time.Now()))
require.NoError(t, err)
}
}
count, err := new(data.Attendance).CountCheckedInUsersByEventID(ctx, ev.EventId)
require.NoError(t, err)
assert.Equal(t, int64(2), count)
}
func TestAttendanceCountWithKycByEventID(t *testing.T) {
testutil.Setup(t)
ctx := context.Background()
owner := uuid.New()
ev := makeEvent(owner)
require.NoError(t, ev.Create(ctx))
for i := 0; i < 3; i++ {
u := data.NewUser(
data.WithEmail(uuid.New().String()+"@test.com"),
data.WithUsername(uuid.New().String()),
data.WithPermissionLevel(10),
)
require.NoError(t, u.Create(ctx))
kycId := uuid.Nil
if i < 2 {
kycId = uuid.New()
}
a := data.NewAttendance(
data.WithEventId(ev.EventId),
data.WithUserId(u.UserId),
data.WithKycId(kycId),
data.WithRole("attendee"),
data.WithState("success"),
)
_, err := a.Create(ctx)
require.NoError(t, err)
}
count, err := new(data.Attendance).CountWithKycByEventID(ctx, ev.EventId)
require.NoError(t, err)
assert.Equal(t, int64(2), count)
}
func TestAttendanceGetAttendanceListFiltered(t *testing.T) {
testutil.Setup(t)
ctx := context.Background()
owner := uuid.New()
ev := makeEvent(owner)
require.NoError(t, ev.Create(ctx))
for i := 0; i < 5; i++ {
u := data.NewUser(
data.WithEmail(uuid.New().String()+"@test.com"),
data.WithUsername(uuid.New().String()),
data.WithNickname(fmt.Sprintf("FilterUser%d", i)),
data.WithPermissionLevel(10),
)
require.NoError(t, u.Create(ctx))
kycId := uuid.Nil
if i < 3 {
kycId = uuid.New()
}
a := data.NewAttendance(
data.WithEventId(ev.EventId),
data.WithUserId(u.UserId),
data.WithKycId(kycId),
data.WithRole("attendee"),
data.WithState("success"),
)
_, err := a.Create(ctx)
require.NoError(t, err)
}
t.Run("no_filters_returns_all", func(t *testing.T) {
list, total, err := new(data.Attendance).GetAttendanceListFiltered(ctx, data.AttendanceListFilter{
EventId: ev.EventId,
Limit: 20,
})
require.NoError(t, err)
assert.Equal(t, int64(5), total)
assert.Len(t, *list, 5)
})
t.Run("with_kyc_filter", func(t *testing.T) {
list, total, err := new(data.Attendance).GetAttendanceListFiltered(ctx, data.AttendanceListFilter{
EventId: ev.EventId,
KycStatus: "with_kyc",
Limit: 20,
})
require.NoError(t, err)
assert.Equal(t, int64(3), total)
assert.Len(t, *list, 3)
})
t.Run("without_kyc_filter", func(t *testing.T) {
list, total, err := new(data.Attendance).GetAttendanceListFiltered(ctx, data.AttendanceListFilter{
EventId: ev.EventId,
KycStatus: "without_kyc",
Limit: 20,
})
require.NoError(t, err)
assert.Equal(t, int64(2), total)
assert.Len(t, *list, 2)
})
t.Run("name_filter", func(t *testing.T) {
list, total, err := new(data.Attendance).GetAttendanceListFiltered(ctx, data.AttendanceListFilter{
EventId: ev.EventId,
Name: "FilterUser1",
Limit: 20,
})
require.NoError(t, err)
assert.Equal(t, int64(1), total)
assert.Len(t, *list, 1)
})
t.Run("pagination", func(t *testing.T) {
list, total, err := new(data.Attendance).GetAttendanceListFiltered(ctx, data.AttendanceListFilter{
EventId: ev.EventId,
Limit: 2,
Offset: 0,
})
require.NoError(t, err)
assert.Equal(t, int64(5), total)
assert.Len(t, *list, 2)
})
t.Run("sort_by_id_asc", func(t *testing.T) {
list, _, err := new(data.Attendance).GetAttendanceListFiltered(ctx, data.AttendanceListFilter{
EventId: ev.EventId,
SortBy: "id",
SortOrder: "asc",
Limit: 20,
})
require.NoError(t, err)
require.NotNil(t, list)
items := *list
for i := 1; i < len(items); i++ {
assert.GreaterOrEqual(t, items[i].Id, items[i-1].Id)
}
})
}
func TestAttendanceCheckinCodeExpiry(t *testing.T) {
mr := testutil.Setup(t)
ctx := context.Background()
eventId, userId := seedEventAndUser(t, ctx)
a := seedAttendance(t, ctx, eventId, userId)
code, err := a.GenCheckinCode(ctx, a.EventId)
require.NoError(t, err)
require.NotNil(t, code)
mr.FastForward(10 * time.Minute)
err = new(data.Attendance).VerifyCheckinCode(ctx, *code)
require.Error(t, err, "expired code should not be valid")
}
func TestAttendanceGetCheckedInEventIDsEmpty(t *testing.T) {
testutil.Setup(t)
ctx := context.Background()
result, err := new(data.Attendance).GetCheckedInEventIDs(ctx, uuid.New(), []uuid.UUID{})
require.NoError(t, err)
assert.Empty(t, result)
}

View File

@@ -156,3 +156,50 @@ func TestEventFastList(t *testing.T) {
require.NoError(t, err)
assert.Len(t, *results, 3)
}
func TestEventGetEventsByUserId(t *testing.T) {
testutil.Setup(t)
ctx := context.Background()
owner := uuid.New()
ev1 := makeEvent(owner)
require.NoError(t, ev1.Create(ctx))
ev2 := makeEvent(owner)
require.NoError(t, ev2.Create(ctx))
ev3 := makeEvent(owner)
require.NoError(t, ev3.Create(ctx))
u := data.NewUser(
data.WithEmail(uuid.New().String()+"@test.com"),
data.WithUsername(uuid.New().String()),
data.WithPermissionLevel(10),
)
require.NoError(t, u.Create(ctx))
for _, evId := range []uuid.UUID{ev1.EventId, ev2.EventId} {
a := data.NewAttendance(
data.WithEventId(evId),
data.WithUserId(u.UserId),
data.WithKycId(uuid.Nil),
data.WithRole("attendee"),
data.WithState("success"),
)
_, err := a.Create(ctx)
require.NoError(t, err)
}
results, err := new(data.Event).GetEventsByUserId(ctx, u.UserId, 10, 0)
require.NoError(t, err)
require.NotNil(t, results)
assert.Len(t, *results, 2)
}
func TestEventGetEventsByUserIdEmpty(t *testing.T) {
testutil.Setup(t)
ctx := context.Background()
results, err := new(data.Event).GetEventsByUserId(ctx, uuid.New(), 10, 0)
require.NoError(t, err)
require.NotNil(t, results)
assert.Empty(t, *results)
}

View File

@@ -96,3 +96,51 @@ func TestGlobalStatsEventJoinCheckinCounts(t *testing.T) {
assert.Equal(t, int64(2), stat.JoinCount)
assert.Equal(t, int64(1), stat.CheckinCount)
}
func TestGlobalStatsEventJoinCheckinCountsEmpty(t *testing.T) {
testutil.Setup(t)
ctx := context.Background()
results, err := new(data.GlobalStats).EventJoinCheckinCounts(ctx)
require.NoError(t, err)
require.NotNil(t, results)
assert.Empty(t, *results)
}
func TestGlobalStatsEventJoinCheckinMultipleEvents(t *testing.T) {
testutil.Setup(t)
ctx := context.Background()
owner := uuid.New()
ev1 := makeEvent(owner)
require.NoError(t, ev1.Create(ctx))
ev2 := makeEvent(owner)
require.NoError(t, ev2.Create(ctx))
for _, evId := range []uuid.UUID{ev1.EventId, ev2.EventId} {
u := data.NewUser(
data.WithEmail(uuid.New().String()+"@test.com"),
data.WithUsername(uuid.New().String()),
data.WithPermissionLevel(10),
)
require.NoError(t, u.Create(ctx))
a := data.NewAttendance(
data.WithEventId(evId),
data.WithUserId(u.UserId),
data.WithKycId(uuid.Nil),
data.WithRole("attendee"),
data.WithState("success"),
)
_, err := a.Create(ctx)
require.NoError(t, err)
}
results, err := new(data.GlobalStats).EventJoinCheckinCounts(ctx)
require.NoError(t, err)
require.NotNil(t, results)
assert.Len(t, *results, 2)
for _, stat := range *results {
assert.Equal(t, int64(1), stat.JoinCount)
assert.Equal(t, int64(0), stat.CheckinCount)
}
}

View File

@@ -101,3 +101,35 @@ func TestBuilderErrorCodeLength(t *testing.T) {
// Status(1) + Endpoint(3) + Service(3) + Type(1) + Original(5) = 13
assert.Len(t, b.ErrorCode, 13)
}
func TestErrorHandlerDoesNotPanic(t *testing.T) {
ctx := context.Background()
testErr := errors.New("test error")
for _, status := range []string{StatusSuccess, StatusUser, StatusServer, StatusClient} {
s := status
assert.NotPanics(t, func() {
ErrorHandler(ctx, s, "code12345678", testErr)
}, "ErrorHandler must not panic for status %q", s)
}
}
func TestErrorHandlerNilError(t *testing.T) {
ctx := context.Background()
assert.NotPanics(t, func() {
ErrorHandler(ctx, StatusUser, "code12345678", nil)
})
}
func TestBuilderNilThrow(t *testing.T) {
b := New(
WithStatus(StatusSuccess),
WithType(TypeCommon),
WithOriginal(CommonSuccess),
)
// Throw on empty context (no service/endpoint set) must not panic
assert.NotPanics(t, func() {
thrown := b.Throw(context.Background())
assert.Equal(t, CommonSuccess, thrown.Original)
})
}

View File

@@ -23,9 +23,9 @@ const (
)
func doPassportRequest(ctx context.Context, method, path string, body any, target any) error {
baseURL := viper.GetString("kyc.passport_reader_endpoint")
publicKey := viper.GetString("kyc.passport_reader_public_key")
secret := viper.GetString("kyc.passport_reader_secret")
baseURL := "https://passportreader.app/api/v1"
var bodyReader io.Reader
if body != nil {

View File

@@ -2,41 +2,190 @@ package kyc
import (
"context"
"encoding/json"
"net/http"
"net/http/httptest"
"testing"
"github.com/spf13/viper"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
// passport.go hardcodes baseURL = "https://passportreader.app/api/v1" with no
// injection point, so we cannot intercept the HTTP calls in tests. These tests
// verify that each function returns an error when the remote server is
// unreachable (no valid credentials in the test environment).
func setupPassportViper(t *testing.T) {
// startMockPassportServer spins up an httptest.Server backed by mux, points
// viper's kyc.passport_reader_endpoint at it, and registers cleanup on t.
func startMockPassportServer(t *testing.T, mux *http.ServeMux) *httptest.Server {
t.Helper()
viper.Set("kyc.passport_reader_public_key", "test-pub-key")
viper.Set("kyc.passport_reader_secret", "test-secret")
t.Cleanup(func() { viper.Reset() })
srv := httptest.NewServer(mux)
viper.Reset()
viper.Set("kyc.passport_reader_endpoint", srv.URL)
viper.Set("kyc.passport_reader_public_key", "pub")
viper.Set("kyc.passport_reader_secret", "sec")
t.Cleanup(func() {
srv.Close()
viper.Reset()
})
return srv
}
func TestCreateSessionReturnsErrorWithoutRealServer(t *testing.T) {
setupPassportViper(t)
func writeJSON(w http.ResponseWriter, v any) {
w.Header().Set("Content-Type", "application/json")
_ = json.NewEncoder(w).Encode(v)
}
// ---- CreateSession ----
func TestCreateSessionSuccess(t *testing.T) {
mux := http.NewServeMux()
mux.HandleFunc("/session.create", func(w http.ResponseWriter, r *http.Request) {
assert.Equal(t, http.MethodPost, r.Method)
writeJSON(w, PassportReaderSessionResponse{ID: 42, Token: "tok"})
})
startMockPassportServer(t, mux)
resp, err := CreateSession(context.Background())
require.NoError(t, err)
assert.Equal(t, 42, resp.ID)
assert.Equal(t, "tok", resp.Token)
}
func TestCreateSessionAPIError(t *testing.T) {
mux := http.NewServeMux()
mux.HandleFunc("/session.create", func(w http.ResponseWriter, r *http.Request) {
http.Error(w, "internal error", http.StatusInternalServerError)
})
startMockPassportServer(t, mux)
_, err := CreateSession(context.Background())
assert.Error(t, err, "CreateSession must fail when the remote server is unreachable")
assert.Error(t, err)
}
func TestGetSessionStateReturnsErrorWithoutRealServer(t *testing.T) {
setupPassportViper(t)
// ---- GetSessionState ----
_, err := GetSessionState(context.Background(), 99999)
assert.Error(t, err, "GetSessionState must fail when the remote server is unreachable")
func TestGetSessionStateSuccess(t *testing.T) {
mux := http.NewServeMux()
mux.HandleFunc("/session.state", func(w http.ResponseWriter, r *http.Request) {
assert.Equal(t, http.MethodPost, r.Method)
assert.Equal(t, "application/json", r.Header.Get("Content-Type"))
var req PassportReaderGetSessionRequest
require.NoError(t, json.NewDecoder(r.Body).Decode(&req))
assert.Equal(t, 42, req.ID)
writeJSON(w, PassportReaderStateResponse{State: StateApproved})
})
startMockPassportServer(t, mux)
state, err := GetSessionState(context.Background(), 42)
require.NoError(t, err)
assert.Equal(t, StateApproved, state)
}
func TestGetSessionDetailsReturnsErrorWithoutRealServer(t *testing.T) {
setupPassportViper(t)
func TestGetSessionStateEachKnownState(t *testing.T) {
knownStates := []string{
StateCreated, StateInitiated, StateCompleted,
StateApproved, StateFailed, StateAborted, StateRejected,
}
_, err := GetSessionDetails(context.Background(), 99999)
assert.Error(t, err, "GetSessionDetails must fail when the remote server is unreachable")
for _, s := range knownStates {
s := s
t.Run(s, func(t *testing.T) {
mux := http.NewServeMux()
mux.HandleFunc("/session.state", func(w http.ResponseWriter, r *http.Request) {
writeJSON(w, PassportReaderStateResponse{State: s})
})
startMockPassportServer(t, mux)
got, err := GetSessionState(context.Background(), 1)
require.NoError(t, err)
assert.Equal(t, s, got)
})
}
}
func TestGetSessionStateAPIError(t *testing.T) {
mux := http.NewServeMux()
mux.HandleFunc("/session.state", func(w http.ResponseWriter, r *http.Request) {
http.Error(w, "bad", http.StatusBadRequest)
})
startMockPassportServer(t, mux)
_, err := GetSessionState(context.Background(), 1)
assert.Error(t, err)
}
// ---- GetSessionDetails ----
func TestGetSessionDetailsSuccess(t *testing.T) {
want := &PassportReaderSessionDetailResponse{
State: StateApproved,
GivenNames: "John",
Surname: "Doe",
Nationality: "USA",
DateOfBirth: "1990-01-01",
DocumentType: "PASSPORT",
DocumentNumber: "X12345678",
ExpiryDate: "2030-01-01",
}
mux := http.NewServeMux()
mux.HandleFunc("/session.get", func(w http.ResponseWriter, r *http.Request) {
assert.Equal(t, http.MethodPost, r.Method)
assert.Equal(t, "application/json", r.Header.Get("Content-Type"))
var req PassportReaderGetSessionRequest
require.NoError(t, json.NewDecoder(r.Body).Decode(&req))
assert.Equal(t, 7, req.ID)
writeJSON(w, want)
})
startMockPassportServer(t, mux)
got, err := GetSessionDetails(context.Background(), 7)
require.NoError(t, err)
assert.Equal(t, want, got)
}
func TestGetSessionDetailsAPIError(t *testing.T) {
mux := http.NewServeMux()
mux.HandleFunc("/session.get", func(w http.ResponseWriter, r *http.Request) {
http.Error(w, "not found", http.StatusNotFound)
})
startMockPassportServer(t, mux)
_, err := GetSessionDetails(context.Background(), 1)
assert.Error(t, err)
}
// ---- HTTP mechanics ----
func TestPassportRequestSendsBasicAuth(t *testing.T) {
mux := http.NewServeMux()
mux.HandleFunc("/session.create", func(w http.ResponseWriter, r *http.Request) {
user, pass, ok := r.BasicAuth()
assert.True(t, ok, "Authorization header must be present")
assert.Equal(t, "pub", user)
assert.Equal(t, "sec", pass)
writeJSON(w, PassportReaderSessionResponse{})
})
startMockPassportServer(t, mux)
_, _ = CreateSession(context.Background())
}
func TestPassportRequestSetsContentTypeForBody(t *testing.T) {
mux := http.NewServeMux()
mux.HandleFunc("/session.state", func(w http.ResponseWriter, r *http.Request) {
assert.Equal(t, "application/json", r.Header.Get("Content-Type"))
writeJSON(w, PassportReaderStateResponse{State: StateCreated})
})
startMockPassportServer(t, mux)
_, _ = GetSessionState(context.Background(), 1)
}
func TestPassportRequestNoContentTypeWithoutBody(t *testing.T) {
mux := http.NewServeMux()
mux.HandleFunc("/session.create", func(w http.ResponseWriter, r *http.Request) {
assert.Empty(t, r.Header.Get("Content-Type"), "no Content-Type when body is nil")
writeJSON(w, PassportReaderSessionResponse{})
})
startMockPassportServer(t, mux)
_, _ = CreateSession(context.Background())
}

View File

@@ -23,7 +23,7 @@ run:
cd {{ output_dir }} && CONFIG_PATH={{ output_dir }} {{ server_exec_path }}{{ if os() == "windows" { ".exe" } else { "" } }}
test:
cd {{ output_dir }} && CONFIG_PATH={{ output_dir }} GO_ENV=test go test -C .. ./...
cd {{ output_dir }} && CONFIG_PATH={{ output_dir }} GO_ENV=test go test -C .. -p 4 ./...
watch:
watchexec -r -e go,yaml,tpl -i '.devenv/**' -i '.direnv/**' -i 'client/**' -i 'vendor/**' 'go build -o {{ server_exec_path }} . && cd {{ output_dir }} && CONFIG_PATH={{ output_dir }} {{ server_exec_path }}'

View File

@@ -225,3 +225,60 @@ func TestPermissionFromDBInsufficient(t *testing.T) {
r.ServeHTTP(w, req)
assert.Equal(t, 403, w.Code)
}
func TestPermissionUserNotFound(t *testing.T) {
testutil.Setup(t)
r := gin.New()
r.Use(func(c *gin.Context) {
c.Set("user_id", uuid.New().String())
c.Next()
})
r.Use(Permission(0))
r.GET("/any", func(c *gin.Context) { c.String(200, "ok") })
req := httptest.NewRequest(http.MethodGet, "/any", nil)
w := httptest.NewRecorder()
r.ServeHTTP(w, req)
assert.Equal(t, 404, w.Code)
}
func TestPermissionCachesLevelFromDB(t *testing.T) {
testutil.Setup(t)
user := testutil.SeedUser(t, testutil.RandomEmail(), 10)
calls := 0
r := gin.New()
r.Use(func(c *gin.Context) {
c.Set("user_id", user.UserId.String())
calls++
c.Next()
})
r.Use(Permission(5))
r.GET("/any", func(c *gin.Context) {
lvl, ok := c.Get("permission_level")
assert.True(t, ok)
assert.Equal(t, uint(10), lvl.(uint))
c.String(200, "ok")
})
req := httptest.NewRequest(http.MethodGet, "/any", nil)
w := httptest.NewRecorder()
r.ServeHTTP(w, req)
assert.Equal(t, 200, w.Code)
}
func TestJWTAuthBadToken(t *testing.T) {
testutil.Setup(t)
testutil.SeedClient(t)
r := gin.New()
r.Use(JWTAuth())
r.GET("/protected", func(c *gin.Context) { c.String(200, "ok") })
req := httptest.NewRequest(http.MethodGet, "/protected", nil)
req.Header.Set("Authorization", "Bearer completely.invalid.token")
w := httptest.NewRecorder()
r.ServeHTTP(w, req)
assert.Equal(t, 401, w.Code)
}

View File

@@ -112,7 +112,8 @@ func TestServerHealthEndpoints(t *testing.T) {
// Response must be valid JSON.
var body map[string]any
_ = json.Unmarshal(w.Body.Bytes(), &body)
err := json.Unmarshal(w.Body.Bytes(), &body)
assert.NoError(t, err, "response must be valid JSON for %s %s", ep.method, ep.path)
})
}
}

View File

@@ -236,6 +236,40 @@ func TestAgendaReviewReject(t *testing.T) {
assert.Equal(t, 200, result.Common.HttpCode)
}
func TestAgendaReviewInvalidStatus(t *testing.T) {
testutil.Setup(t)
ctx := context.Background()
event, user, _ := seedAgendaTestFixture(t, ctx)
svc := newAgendaSvc()
submitResult := svc.Submit(&SubmitPayload{
Context: ctx,
UserId: user.UserId,
Data: &SubmitData{EventId: event.EventId, Name: "Talk"},
})
require.Equal(t, 200, submitResult.Common.HttpCode)
manager := testutil.SeedUser(t, testutil.RandomEmail(), 30)
result := svc.Review(&AgendaReviewPayload{
Context: ctx,
UserId: manager.UserId,
Data: &AgendaReviewData{
EventId: event.EventId,
AgendaId: submitResult.Data.AgendaId,
Status: "banana",
},
})
assert.Equal(t, 400, result.Common.HttpCode)
// Verify the agenda status was not mutated in the database.
ag, err := new(data.Agenda).GetByAgendaId(ctx, submitResult.Data.AgendaId)
require.NoError(t, err)
assert.Equal(t, "pending", ag.Status)
}
// ---- List ----
func TestAgendaList(t *testing.T) {
@@ -313,6 +347,215 @@ func TestAgendaScheduleGetNotPublished(t *testing.T) {
assert.Equal(t, exception.AgendaScheduleGetNotPublished, result.Common.Exception.Original)
}
// ---- Update ----
func TestAgendaUpdateSubmitterSuccess(t *testing.T) {
testutil.Setup(t)
ctx := context.Background()
event, user, _ := seedAgendaTestFixture(t, ctx)
svc := newAgendaSvc()
submitResult := svc.Submit(&SubmitPayload{
Context: ctx,
UserId: user.UserId,
Data: &SubmitData{EventId: event.EventId, Name: "Original Name"},
})
require.Equal(t, 200, submitResult.Common.HttpCode)
newName := "Updated Name"
result := svc.Update(&AgendaUpdatePayload{
Context: ctx,
UserId: user.UserId,
Data: &AgendaUpdateData{
AgendaId: submitResult.Data.AgendaId,
Name: &newName,
PermissionLevel: 10,
},
})
assert.Equal(t, 200, result.Common.HttpCode)
ag, err := new(data.Agenda).GetByAgendaId(ctx, submitResult.Data.AgendaId)
require.NoError(t, err)
assert.Equal(t, "Updated Name", ag.Name)
}
func TestAgendaUpdateManagerCanEdit(t *testing.T) {
testutil.Setup(t)
ctx := context.Background()
event, user, _ := seedAgendaTestFixture(t, ctx)
svc := newAgendaSvc()
submitResult := svc.Submit(&SubmitPayload{
Context: ctx,
UserId: user.UserId,
Data: &SubmitData{EventId: event.EventId, Name: "Original Name"},
})
require.Equal(t, 200, submitResult.Common.HttpCode)
manager := testutil.SeedUser(t, testutil.RandomEmail(), 30)
newName := "Manager Updated"
result := svc.Update(&AgendaUpdatePayload{
Context: ctx,
UserId: manager.UserId,
Data: &AgendaUpdateData{
AgendaId: submitResult.Data.AgendaId,
Name: &newName,
PermissionLevel: 30,
},
})
assert.Equal(t, 200, result.Common.HttpCode)
ag, err := new(data.Agenda).GetByAgendaId(ctx, submitResult.Data.AgendaId)
require.NoError(t, err)
assert.Equal(t, "Manager Updated", ag.Name)
}
func TestAgendaUpdateNotSubmitter(t *testing.T) {
testutil.Setup(t)
ctx := context.Background()
event, user, _ := seedAgendaTestFixture(t, ctx)
svc := newAgendaSvc()
submitResult := svc.Submit(&SubmitPayload{
Context: ctx,
UserId: user.UserId,
Data: &SubmitData{EventId: event.EventId, Name: "My Talk"},
})
require.Equal(t, 200, submitResult.Common.HttpCode)
other := testutil.SeedUser(t, testutil.RandomEmail(), 10)
newName := "Hijacked"
result := svc.Update(&AgendaUpdatePayload{
Context: ctx,
UserId: other.UserId,
Data: &AgendaUpdateData{
AgendaId: submitResult.Data.AgendaId,
Name: &newName,
PermissionLevel: 10,
},
})
assert.Equal(t, 403, result.Common.HttpCode)
assert.Equal(t, exception.AgendaUpdateNotSubmitter, result.Common.Exception.Original)
}
func TestAgendaUpdateNotPending(t *testing.T) {
testutil.Setup(t)
ctx := context.Background()
event, user, _ := seedAgendaTestFixture(t, ctx)
svc := newAgendaSvc()
submitResult := svc.Submit(&SubmitPayload{
Context: ctx,
UserId: user.UserId,
Data: &SubmitData{EventId: event.EventId, Name: "My Talk"},
})
require.Equal(t, 200, submitResult.Common.HttpCode)
manager := testutil.SeedUser(t, testutil.RandomEmail(), 30)
svc.Review(&AgendaReviewPayload{
Context: ctx,
UserId: manager.UserId,
Data: &AgendaReviewData{
EventId: event.EventId,
AgendaId: submitResult.Data.AgendaId,
Status: "approved",
},
})
newName := "Try to Edit"
result := svc.Update(&AgendaUpdatePayload{
Context: ctx,
UserId: user.UserId,
Data: &AgendaUpdateData{
AgendaId: submitResult.Data.AgendaId,
Name: &newName,
PermissionLevel: 10,
},
})
assert.Equal(t, 400, result.Common.HttpCode)
assert.Equal(t, exception.AgendaUpdateNotPending, result.Common.Exception.Original)
}
func TestAgendaUpdateDeadlinePassed(t *testing.T) {
testutil.Setup(t)
ctx := context.Background()
user := testutil.SeedUser(t, testutil.RandomEmail(), 10)
require.NoError(t, new(data.User).PatchByUserId(ctx, user.UserId, data.WithNickname("Attendee")))
owner := testutil.SeedUser(t, testutil.RandomEmail(), 40)
event := data.NewEvent(
data.WithOwner(owner.UserId),
data.WithEventType("party"),
data.WithEventName("Past Event"),
data.WithEventSubtitle("sub"),
data.WithEventStartTime(time.Now().Add(-2*time.Hour)),
data.WithEventEndTime(time.Now().Add(-time.Hour)),
data.WithQuota(100),
data.WithLimit(150),
)
require.NoError(t, event.Create(ctx))
att := data.NewAttendance(
data.WithEventId(event.EventId),
data.WithUserId(user.UserId),
data.WithKycId(uuid.Nil),
data.WithRole("attendee"),
data.WithState("success"),
)
_, err := att.Create(ctx)
require.NoError(t, err)
ag := data.NewAgenda(
data.WithAttendanceId(att.AttendanceId),
data.WithAgendaName("Past Talk"),
data.WithAgendaDescription("desc"),
data.WithAgendaStatus("pending"),
)
require.NoError(t, ag.Create(ctx))
newName := "Updated Past"
svc := newAgendaSvc()
result := svc.Update(&AgendaUpdatePayload{
Context: ctx,
UserId: user.UserId,
Data: &AgendaUpdateData{
AgendaId: ag.AgendaId,
Name: &newName,
PermissionLevel: 10,
},
})
assert.Equal(t, 400, result.Common.HttpCode)
assert.Equal(t, exception.AgendaUpdateDeadlinePassed, result.Common.Exception.Original)
}
func TestAgendaUpdateNotFound(t *testing.T) {
testutil.Setup(t)
svc := newAgendaSvc()
newName := "Phantom"
result := svc.Update(&AgendaUpdatePayload{
Context: context.Background(),
UserId: uuid.New(),
Data: &AgendaUpdateData{
AgendaId: uuid.New(),
Name: &newName,
PermissionLevel: 10,
},
})
assert.Equal(t, 404, result.Common.HttpCode)
}
func TestAgendaScheduleGetPublished(t *testing.T) {
testutil.Setup(t)
ctx := context.Background()

View File

@@ -109,6 +109,20 @@ func (self *AgendaServiceImpl) Review(payload *AgendaReviewPayload) (result *Age
return
}
if payload.Data.Status != "approved" && payload.Data.Status != "rejected" {
exc := exception.New(
exception.WithStatus(exception.StatusUser),
exception.WithType(exception.TypeCommon),
exception.WithOriginal(exception.CommonErrorInvalidInput),
exception.WithError(errors.New("status must be approved or rejected")),
).Throw(ctx)
result = &AgendaReviewResult{
Common: shared.CommonResult{HttpCode: 400, Exception: exc},
}
return
}
if err := new(data.Agenda).PatchByAgendaId(ctx, payload.Data.AgendaId,
data.WithAgendaStatus(payload.Data.Status),
); err != nil {

View File

@@ -13,6 +13,7 @@ import (
"nixcn-cms/testutil"
)
func newEventSvc() EventService { return NewEventService() }
func makeOwner(t *testing.T, permLevel uint) *data.User {
@@ -431,3 +432,311 @@ func TestEventStatsNotOwner(t *testing.T) {
assert.Equal(t, 403, result.Common.HttpCode)
assert.Equal(t, exception.EventStatsNotOwner, result.Common.Exception.Original)
}
// ---- Checkin ----
func TestEventCheckinSuccess(t *testing.T) {
testutil.Setup(t)
ctx := context.Background()
owner := makeOwner(t, 40)
joiner := makeOwner(t, 10)
require.NoError(t, new(data.User).PatchByUserId(ctx, joiner.UserId, data.WithNickname("Joiner")))
cr := createEventForOwner(t, ctx, owner, "party")
require.Equal(t, 200, cr.Common.HttpCode)
svc := newEventSvc()
jr := svc.Join(&EventJoinPayload{
Context: ctx,
Data: &EventJoinData{
EventId: cr.Data.EventId,
UserId: joiner.UserId.String(),
},
})
require.Equal(t, 200, jr.Common.HttpCode)
eventId, _ := uuid.Parse(cr.Data.EventId)
result := svc.Checkin(&CheckinPayload{
Context: ctx,
UserId: joiner.UserId,
Data: &CheckinData{EventId: eventId},
})
assert.Equal(t, 200, result.Common.HttpCode)
require.NotNil(t, result.Data)
require.NotNil(t, result.Data.CheckinCode)
assert.Len(t, *result.Data.CheckinCode, 6)
}
func TestEventCheckinNotAttendee(t *testing.T) {
testutil.Setup(t)
ctx := context.Background()
owner := makeOwner(t, 40)
notJoiner := makeOwner(t, 10)
cr := createEventForOwner(t, ctx, owner, "party")
require.Equal(t, 200, cr.Common.HttpCode)
eventId, _ := uuid.Parse(cr.Data.EventId)
svc := newEventSvc()
result := svc.Checkin(&CheckinPayload{
Context: ctx,
UserId: notJoiner.UserId,
Data: &CheckinData{EventId: eventId},
})
assert.Equal(t, 403, result.Common.HttpCode)
}
// ---- CheckinSubmit ----
func TestEventCheckinSubmitSuccess(t *testing.T) {
testutil.Setup(t)
ctx := context.Background()
owner := makeOwner(t, 40)
joiner := makeOwner(t, 10)
require.NoError(t, new(data.User).PatchByUserId(ctx, joiner.UserId, data.WithNickname("Joiner")))
cr := createEventForOwner(t, ctx, owner, "party")
require.Equal(t, 200, cr.Common.HttpCode)
svc := newEventSvc()
jr := svc.Join(&EventJoinPayload{
Context: ctx,
Data: &EventJoinData{
EventId: cr.Data.EventId,
UserId: joiner.UserId.String(),
},
})
require.Equal(t, 200, jr.Common.HttpCode)
eventId, _ := uuid.Parse(cr.Data.EventId)
checkinResult := svc.Checkin(&CheckinPayload{
Context: ctx,
UserId: joiner.UserId,
Data: &CheckinData{EventId: eventId},
})
require.Equal(t, 200, checkinResult.Common.HttpCode)
submitResult := svc.CheckinSubmit(&CheckinSubmitPayload{
Context: ctx,
Data: &CheckinSubmitData{CheckinCode: *checkinResult.Data.CheckinCode},
})
assert.Equal(t, 200, submitResult.Common.HttpCode)
}
func TestEventCheckinSubmitInvalidCode(t *testing.T) {
testutil.Setup(t)
svc := newEventSvc()
result := svc.CheckinSubmit(&CheckinSubmitPayload{
Context: context.Background(),
Data: &CheckinSubmitData{CheckinCode: "000000"},
})
assert.Equal(t, 400, result.Common.HttpCode)
}
// ---- CheckinQuery ----
func TestEventCheckinQueryNotCheckedIn(t *testing.T) {
testutil.Setup(t)
ctx := context.Background()
owner := makeOwner(t, 40)
joiner := makeOwner(t, 10)
require.NoError(t, new(data.User).PatchByUserId(ctx, joiner.UserId, data.WithNickname("Joiner")))
cr := createEventForOwner(t, ctx, owner, "party")
require.Equal(t, 200, cr.Common.HttpCode)
svc := newEventSvc()
jr := svc.Join(&EventJoinPayload{
Context: ctx,
Data: &EventJoinData{
EventId: cr.Data.EventId,
UserId: joiner.UserId.String(),
},
})
require.Equal(t, 200, jr.Common.HttpCode)
eventId, _ := uuid.Parse(cr.Data.EventId)
result := svc.CheckinQuery(&CheckinQueryPayload{
Context: ctx,
UserId: joiner.UserId,
Data: &CheckinQueryData{EventId: eventId},
})
assert.Equal(t, 200, result.Common.HttpCode)
require.NotNil(t, result.Data)
assert.Nil(t, result.Data.CheckinAt)
}
func TestEventCheckinQueryNotFound(t *testing.T) {
testutil.Setup(t)
svc := newEventSvc()
result := svc.CheckinQuery(&CheckinQueryPayload{
Context: context.Background(),
UserId: uuid.New(),
Data: &CheckinQueryData{EventId: uuid.New()},
})
assert.Equal(t, 404, result.Common.HttpCode)
}
// ---- AttendanceList ----
func TestEventAttendanceListSuccess(t *testing.T) {
testutil.Setup(t)
ctx := context.Background()
owner := makeOwner(t, 40)
cr := createEventForOwner(t, ctx, owner, "party")
require.Equal(t, 200, cr.Common.HttpCode)
eventId, _ := uuid.Parse(cr.Data.EventId)
svc := newEventSvc()
for i := 0; i < 2; i++ {
joiner := makeOwner(t, 10)
require.NoError(t, new(data.User).PatchByUserId(ctx, joiner.UserId, data.WithNickname("Joiner")))
jr := svc.Join(&EventJoinPayload{
Context: ctx,
Data: &EventJoinData{
EventId: cr.Data.EventId,
UserId: joiner.UserId.String(),
},
})
require.Equal(t, 200, jr.Common.HttpCode)
}
limit := "10"
offset := "0"
result := svc.AttendanceList(&AttendanceListPayload{
Context: ctx,
UserId: owner.UserId,
Data: &AttendanceListData{
EventId: eventId,
Limit: &limit,
Offset: &offset,
},
})
assert.Equal(t, 200, result.Common.HttpCode)
require.NotNil(t, result.Data)
assert.Equal(t, int64(2), result.Data.Total)
assert.Len(t, result.Data.Items, 2)
}
func TestEventAttendanceListNotOwner(t *testing.T) {
testutil.Setup(t)
ctx := context.Background()
owner := makeOwner(t, 40)
notOwner := makeOwner(t, 40)
cr := createEventForOwner(t, ctx, owner, "party")
require.Equal(t, 200, cr.Common.HttpCode)
eventId, _ := uuid.Parse(cr.Data.EventId)
svc := newEventSvc()
result := svc.AttendanceList(&AttendanceListPayload{
Context: ctx,
UserId: notOwner.UserId,
Data: &AttendanceListData{EventId: eventId},
})
assert.Equal(t, 403, result.Common.HttpCode)
}
func TestEventAttendanceListNotFound(t *testing.T) {
testutil.Setup(t)
svc := newEventSvc()
result := svc.AttendanceList(&AttendanceListPayload{
Context: context.Background(),
UserId: uuid.New(),
Data: &AttendanceListData{EventId: uuid.New()},
})
assert.Equal(t, 404, result.Common.HttpCode)
}
// ---- GetAttendanceGuide ----
func TestEventGetAttendanceGuideSuccess(t *testing.T) {
testutil.Setup(t)
ctx := context.Background()
owner := makeOwner(t, 40)
joiner := makeOwner(t, 10)
require.NoError(t, new(data.User).PatchByUserId(ctx, joiner.UserId, data.WithNickname("Joiner")))
cr := createEventForOwner(t, ctx, owner, "party")
require.Equal(t, 200, cr.Common.HttpCode)
eventId, _ := uuid.Parse(cr.Data.EventId)
require.NoError(t, new(data.Event).PatchByEventId(ctx, eventId, data.WithAttendanceGuide("base64guidetext")))
svc := newEventSvc()
jr := svc.Join(&EventJoinPayload{
Context: ctx,
Data: &EventJoinData{
EventId: cr.Data.EventId,
UserId: joiner.UserId.String(),
},
})
require.Equal(t, 200, jr.Common.HttpCode)
result := svc.GetAttendanceGuide(&AttendanceGuidePayload{
Context: ctx,
UserId: joiner.UserId,
Data: &AttendanceGuideData{EventId: eventId},
})
assert.Equal(t, 200, result.Common.HttpCode)
require.NotNil(t, result.Data)
assert.Equal(t, "base64guidetext", result.Data.AttendanceGuide)
}
// ---- Join edge cases ----
func TestEventJoinLimitExceeded(t *testing.T) {
testutil.Setup(t)
ctx := context.Background()
owner := makeOwner(t, 40)
svc := newEventSvc()
cr := svc.Create(&EventCreatePayload{
Context: ctx,
Data: &EventCreateData{
UserId: owner.UserId.String(),
PermissionLevel: owner.PermissionLevel,
Type: "party",
Name: "Zero Capacity Event",
Subtitle: "sub",
StartTime: time.Now().Add(time.Hour),
EndTime: time.Now().Add(2 * time.Hour),
Quota: 0,
Limit: 0,
},
})
require.Equal(t, 200, cr.Common.HttpCode)
joiner := makeOwner(t, 10)
require.NoError(t, new(data.User).PatchByUserId(ctx, joiner.UserId, data.WithNickname("Joiner")))
result := svc.Join(&EventJoinPayload{
Context: ctx,
Data: &EventJoinData{
EventId: cr.Data.EventId,
UserId: joiner.UserId.String(),
},
})
assert.Equal(t, 403, result.Common.HttpCode)
assert.Equal(t, exception.EventJoinLimitExceeded, result.Common.Exception.Original)
}

View File

@@ -3,9 +3,14 @@ package service_kyc
import (
"context"
"encoding/base64"
"encoding/json"
"net/http"
"net/http/httptest"
"testing"
"github.com/spf13/viper"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"nixcn-cms/testutil"
)
@@ -87,14 +92,97 @@ func TestSessionKycInvalidType(t *testing.T) {
func TestQueryKycExternalAPIError(t *testing.T) {
testutil.Setup(t)
// PassportReaderSessionId is 0 (zero value), calling GetSessionState will
// fail with a network/API error since there is no real PassportReader server.
// kyc.passport_reader_endpoint is unset, so GetSessionState will fail.
svc := newKycSvc()
result := svc.QueryKyc(&KycQueryPayload{
Context: context.Background(),
Data: &KycQueryData{KycId: "00000000-0000-0000-0000-000000000001"},
})
// External API is unavailable in test → expect a 400 error response
assert.Equal(t, 400, result.Common.HttpCode)
}
func TestQueryKycUnknownSessionState(t *testing.T) {
testutil.Setup(t)
// Start a mock server that returns a state string matching no known branch.
mux := http.NewServeMux()
mux.HandleFunc("/session.state", func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
_ = json.NewEncoder(w).Encode(map[string]string{"state": "UNKNOWN_STATE_XYZ"})
})
srv := httptest.NewServer(mux)
t.Cleanup(srv.Close)
viper.Set("kyc.passport_reader_endpoint", srv.URL)
svc := &KycServiceImpl{PassportReaderSessionId: 1}
result := svc.QueryKyc(&KycQueryPayload{
Context: context.Background(),
Data: &KycQueryData{KycId: "00000000-0000-0000-0000-000000000001"},
})
require.NotNil(t, result, "result must never be nil")
assert.Equal(t, 500, result.Common.HttpCode)
}
func TestQueryKycPendingStates(t *testing.T) {
pendingStates := []string{"CREATED", "INITIATED", "COMPLETED"}
for _, s := range pendingStates {
s := s
t.Run(s, func(t *testing.T) {
testutil.Setup(t)
mux := http.NewServeMux()
mux.HandleFunc("/session.state", func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
_ = json.NewEncoder(w).Encode(map[string]string{"state": s})
})
srv := httptest.NewServer(mux)
t.Cleanup(srv.Close)
viper.Set("kyc.passport_reader_endpoint", srv.URL)
svc := &KycServiceImpl{PassportReaderSessionId: 1}
result := svc.QueryKyc(&KycQueryPayload{
Context: context.Background(),
Data: &KycQueryData{KycId: "00000000-0000-0000-0000-000000000001"},
})
require.NotNil(t, result)
assert.Equal(t, 200, result.Common.HttpCode)
require.NotNil(t, result.Data)
assert.Equal(t, "pending", result.Data.Status)
})
}
}
func TestQueryKycFailedStates(t *testing.T) {
failedStates := []string{"FAILED", "ABORTED", "REJECTED"}
for _, s := range failedStates {
s := s
t.Run(s, func(t *testing.T) {
testutil.Setup(t)
mux := http.NewServeMux()
mux.HandleFunc("/session.state", func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
_ = json.NewEncoder(w).Encode(map[string]string{"state": s})
})
srv := httptest.NewServer(mux)
t.Cleanup(srv.Close)
viper.Set("kyc.passport_reader_endpoint", srv.URL)
svc := &KycServiceImpl{PassportReaderSessionId: 1}
result := svc.QueryKyc(&KycQueryPayload{
Context: context.Background(),
Data: &KycQueryData{KycId: "00000000-0000-0000-0000-000000000001"},
})
require.NotNil(t, result)
assert.Equal(t, 200, result.Common.HttpCode)
require.NotNil(t, result.Data)
assert.Equal(t, "failed", result.Data.Status)
})
}
}

View File

@@ -2,6 +2,7 @@ package service_kyc
import (
"context"
"errors"
"nixcn-cms/data"
"nixcn-cms/internal/exception"
"nixcn-cms/internal/kyc"
@@ -229,5 +230,18 @@ func (self *KycServiceImpl) QueryKyc(payload *KycQueryPayload) (result *KycQuery
return
}
exc := exception.New(
exception.WithStatus(exception.StatusServer),
exception.WithType(exception.TypeCommon),
exception.WithOriginal(exception.CommonErrorInvalidInput),
exception.WithError(errors.New("unknown session state: "+string(sessionState))),
).Throw(ctx)
result = &KycQueryResult{
Common: shared.CommonResult{
HttpCode: 500,
Exception: exc,
},
}
return
}

View File

@@ -49,3 +49,16 @@ func TestGlobalStatsWithData(t *testing.T) {
assert.Equal(t, int64(2), countMap[10])
assert.Equal(t, int64(1), countMap[30])
}
func TestGlobalStatsEventJoinCheckinField(t *testing.T) {
testutil.Setup(t)
ctx := context.Background()
svc := NewStatsService()
result := svc.Global(&GlobalStatsPayload{Context: ctx})
assert.Equal(t, 200, result.Common.HttpCode)
require.NotNil(t, result.Data)
require.NotNil(t, result.Data.EventJoinCheckin, "EventJoinCheckin field must not be nil")
assert.Empty(t, *result.Data.EventJoinCheckin)
}