package httpapi import ( "strings" "testing" "time" "git.118im.com/im_team/im_workbench/internal/store" ) func TestPayloadUserIDsAreStrings(t *testing.T) { now := time.Date(2026, 9, 16, 9, 30, 0, 0, time.UTC) userA, err := parseUserID("usrag7w9xJ6hto") if err != nil { t.Fatal(err) } userB, err := parseUserID("usr1RqTFgHSenM") if err != nil { t.Fatal(err) } payloads := []map[string]any{ organizationMemberPayload(store.OrganizationMember{UserID: userA}), attendanceRecordPayload(&store.AttendanceRecord{RecordID: "att_1", UserID: userA, Date: now, CreatedAt: now, UpdatedAt: now}), attendanceAppealPayload(&store.AttendanceAppeal{AppealID: "atta_1", UserID: userA, SubmittedAt: now, CreatedAt: now, UpdatedAt: now}), meetingPayload(store.Meeting{ MeetingID: "mtg_1", OrganizerID: userA, RecorderID: userB, StartAt: now, EndAt: now.Add(time.Hour), CreatedAt: now, UpdatedAt: now, Participants: []store.MeetingParticipant{ {UserID: userA, Response: "accepted"}, {UserID: userB, Response: "pending"}, }, }, userA), meetingNoteFilePayload(store.MeetingNoteFile{NoteFileID: "mnote_1", UploaderID: userA, CreatedAt: now, UpdatedAt: now}, userA), dailyReportPayload(store.DailyReport{ReportID: "dr_1", AuthorID: userA, ReportDate: now, CreatedAt: now, UpdatedAt: now}), monthlyReportPayload(store.MonthlyReport{ReportID: "mr_1", AuthorID: userA, CreatedAt: now, UpdatedAt: now}), mailboxSubmissionPayload(store.MailboxSubmission{SubmissionID: "mb_1", SenderID: userA, SubmittedAt: now}), } for _, payload := range payloads { assertNoNumericUserIDs(t, payload) } } func TestMeetingPayloadIncludesNamesAndDerivedEndedState(t *testing.T) { now := time.Now().UTC() userA, err := parseUserID("usrag7w9xJ6hto") if err != nil { t.Fatal(err) } userB, err := parseUserID("usr1RqTFgHSenM") if err != nil { t.Fatal(err) } payload := meetingPayload(store.Meeting{ MeetingID: "mtg_1", Title: "周会", OrganizerID: userA, RecorderID: userA, Organizer: store.UserSummary{UserID: userA, DisplayName: "张三"}, Recorder: store.UserSummary{UserID: userA, DisplayName: "张三"}, StartAt: now.Add(-2 * time.Hour), EndAt: now.Add(-time.Hour), State: "scheduled", CreatedAt: now, UpdatedAt: now, Participants: []store.MeetingParticipant{ {UserID: userB, Response: "pending", User: store.UserSummary{UserID: userB, DisplayName: "李四"}}, }, }, userA) if payload["state"] != "ended" { t.Fatalf("state=%v, want ended", payload["state"]) } if payload["organizer_display_name"] != "张三" { t.Fatalf("organizer_display_name=%v", payload["organizer_display_name"]) } names, ok := payload["participant_names"].([]string) if !ok || len(names) != 1 || names[0] != "李四" { t.Fatalf("participant_names=%#v", payload["participant_names"]) } participants, ok := payload["participants"].([]map[string]any) if !ok || len(participants) != 1 || participants[0]["display_name"] != "李四" { t.Fatalf("participants=%#v", payload["participants"]) } } func TestMeetingNoteFilePayloadKeepsOriginalNameAndExtension(t *testing.T) { now := time.Date(2026, 9, 17, 12, 0, 0, 0, time.UTC) payload := meetingNoteFilePayload(store.MeetingNoteFile{ NoteFileID: "mnote_1", FileID: "file_1", FileName: "会议纪要.pdf", MIMEType: "application/pdf", FileSize: 2048, UploaderID: 1, Status: "active", CreatedAt: now, UpdatedAt: now, }, 1) if payload["file_name"] != "会议纪要.pdf" || payload["name"] != "会议纪要.pdf" { t.Fatalf("payload names = file_name:%v name:%v", payload["file_name"], payload["name"]) } if payload["file_extension"] != "pdf" { t.Fatalf("file_extension = %v, want pdf", payload["file_extension"]) } if payload["mime"] != "application/pdf" || payload["size"] != int64(2048) { t.Fatalf("payload aliases = mime:%v size:%v", payload["mime"], payload["size"]) } } func TestMeetingNoteFilePayloadUsesUploaderSummary(t *testing.T) { now := time.Date(2026, 9, 17, 12, 0, 0, 0, time.UTC) payload := meetingNoteFilePayload(store.MeetingNoteFile{ NoteFileID: "mnote_1", UploaderID: 1573637795346386944, Uploader: store.UserSummary{ UserID: 1573637795346386944, DisplayName: "jcharles3", }, Status: "active", CreatedAt: now, UpdatedAt: now, }, 1573637795346386944) uploader, ok := payload["uploader"].(map[string]any) if !ok { t.Fatalf("uploader=%#v", payload["uploader"]) } if uploader["display_name"] != "jcharles3" { t.Fatalf("uploader.display_name=%v, want jcharles3", uploader["display_name"]) } } func assertNoNumericUserIDs(t *testing.T, value any) { t.Helper() switch typed := value.(type) { case map[string]any: for key, item := range typed { if isUserIDKey(key) { if _, ok := item.(string); !ok { t.Fatalf("%s is %T, want string", key, item) } } assertNoNumericUserIDs(t, item) } case []map[string]any: for _, item := range typed { assertNoNumericUserIDs(t, item) } case []any: for _, item := range typed { assertNoNumericUserIDs(t, item) } } } func isUserIDKey(key string) bool { return key == "user_id" || key == "organizer_id" || key == "recorder_user_id" || key == "uploader_user_id" || key == "author_id" || key == "sender_id" || strings.HasSuffix(key, "_user_id") }