Product Requirements · Test Reference

Fun Chat

A lightweight instant messenger for small teams and friend groups — one conversation across Android, iOS and the web, with the moderation tooling an operator needs to run it. Written for the people testing it: every requirement states what correct behaviour looks like, and §7 lists what is already known to be wrong.

Version
1.1
Date
7 Sep 2026
Audience
QA
Build status
Beta — feature complete

1Summary

What the product is and where it stands today.

Fun Chat is a real-time messaging product: one-to-one and group conversations, media, read receipts, and the group-management controls people expect once a chat has more than a handful of participants. It ships as a native Android app, a native iOS app, and two web clients that share one codebase — a phone layout and a three-pane desktop layout — plus a separate console for moderation.

The server is a single Go binary exposing 93 HTTP routes and one WebSocket endpoint. Every client speaks the same API; the web clients are served from the same origin as the API they call, so no client needs cross-origin permission to work.

Status. The feature set below is built and verified end to end. What separates this from a launch candidate is listed in §7 — chiefly unauthenticated media delivery, and a storage backend that currently keeps everything in memory.

2Test environments & accounts

Where to test, what is signed in already, and what a restart takes away.

2.1 Environments

Two deployments run the same build. The first is the one to use; the second answers on its own address and is how to tell a server fault apart from something the CDN in front of the first one did.

EnvironmentAddressWhat it serves
Primary api.youxin.chat API and WebSocket, behind a CDN. Web clients at /h5/, /pc/ and /admin/.
Marketing youxin.chat Landing page. The Android build downloads from /downloads/funchat-release.apk.
Direct 155.138.164.68.nip.io The same three web clients on the same paths, with no CDN in the way.

Use the path form, not the subdomain. h5.youxin.chat, pc.youxin.chat and admin.youxin.chat appear in the deployment design (§12) but have no DNS record yet, so they do not resolve. Everything is reachable today under api.youxin.chat/h5/, /pc/ and /admin/.

2.2 Standing accounts

Three member accounts are recreated automatically after every server start. The password for all three is FunChat2026.

AccountDisplay nameStarts out
test01 Test One 测试一号 Contact of test02, with a two-message 1:1 thread. Owns the seeded group. Has a request from test03 waiting.
test02 Test Two 测试二号 Contact of test01. Plain member of the group.
test03 Test Three 测试三号 Nobody’s contact yet. Plain member of the group. Its pending request to test01 is the fixture for the accept / decline flow.

The seeded group is 产品设计组 (“Product Design”) — all three accounts, an announcement set, three messages. Registration is open, so more accounts can be created freely; nothing has to be requested.

Console credentials are deliberately not printed in this document. They live in the environment file on the server; ask whoever owns the deployment.

Everything is held in memory. A restart erases every account, message and upload, and then recreates exactly the state above. So a defect that depends on data you created is only reproducible until the next restart: capture the request and the response while you still have them, and prefer a reproduction that starts from the seeded state. This is OI-2 in §7 — it is known, and not something to report.

2.3 The Android build

The release APK is signed and minified and refuses cleartext HTTP, so it will only talk to an HTTPS host. Installing it needs “install unknown apps” granted to whichever browser downloads it. It is not a debug build: stack traces come back obfuscated, so record the steps rather than relying on the trace.

3Platform scope

Where each capability is available. The two web clients share one implementation.

CapabilityAndroidiOSWeb phoneWeb desktopConsole
Accounts & sessions
1:1 and group messaging
Media send & preview
Group administration
In-conversation search
Push notifications
Moderation & audit

available   not applicable to that surface

4Functional requirements

Grouped by area. Status reflects verified behaviour on the current build, not intent.

4.1 Accounts & identity

FR-1.1
Self-service registrationAccount name and password only. No email or phone verification in this release.
Shipped
FR-1.2
Password policy8–128 characters, letters and digits required. Enforced identically on registration and change.
Shipped
FR-1.3
Access and refresh tokensOne-hour access token with a refresh token. Tokens are never accepted from the URL — only an Authorization header.
Shipped
FR-1.4
Session list and remote sign-outA person can see their active sessions and end any one of them. Changing the password ends all of them.
Shipped
FR-1.5
Account deletionRemoves the account, its sessions, and its presence in other people's contact lists.
Shipped
FR-1.6
Sign-in throttlingFive failures locks the account for fifteen minutes; a correct password during the lock still fails.
Shipped

4.2 Contacts & safety

FR-2.1
Find people by account nameExact and partial match. Wildcard characters are treated as literals, so search cannot be used to enumerate the directory.
Shipped
FR-2.2
Friend requestsRequest, list, accept. Only the addressee may accept; accepting opens the conversation.
Shipped
FR-2.3
Private remarksA contact can be renamed for the viewer only; the label never reaches the other side.
Shipped
FR-2.4
Blocking stops deliveryBlocking in either direction prevents sending in that 1:1 conversation. Group conversations are unaffected — blocking one member must not silence a group.
Shipped
FR-2.5
Reporting with evidenceA report may cite a specific message, and is accepted only if the reporter can actually see it. Repeat reports against the same person are rate-limited to one per ten minutes.
Shipped

4.3 Conversations & groups

FR-3.1
Membership gates historyConversation identifiers are derived from participant ids (single-<low>-<high>) and are therefore guessable, so every read is authorised against membership rather than against the identifier.
Shipped
FR-3.2
Per-person conversation settingsPin and mute are stored per viewer and never leak to the other participant.
Shipped
FR-3.3
Clear and remove, one-sidedClearing history or removing a conversation affects only the person who did it. A new message restores a removed conversation.
Shipped
FR-3.4
Group rolesOwner, admin, member. Only the owner assigns roles, and never to themselves.
Shipped
FR-3.5
Group administrationRename, announcement, invite, remove, and dissolve. Owner-only except announcements and invitations, which admins may also perform.
Shipped
FR-3.6
Muting a memberSet as an expiry time, so it lifts on its own. Owners cannot be muted; a muted member is refused at send time with an explanation.
Shipped
FR-3.7
Per-group display nameEach member sets their own name within a group; it cannot be set on anyone else's behalf.
Shipped
FR-3.8
Member list is private to the groupEnforced at the API layer — the roster is not readable by identifier alone.
Shipped

4.4 Messaging

FR-4.1
Ordered, resumable historyEvery message carries a per-conversation sequence number; clients resume by requesting everything after the last sequence they hold.
Shipped
FR-4.2
Retry never duplicatesA client-supplied message id makes send idempotent, so a retry after a dropped connection returns the original message.
Shipped
FR-4.3
Quote and forwardBoth preserve a reference to the source message.
Shipped
FR-4.4
Recall within two minutesSender only. The message becomes a recall marker for everyone, including recipients who already received it.
Shipped
FR-4.5
Delete for meHides a message for one person; the other participants keep it.
Shipped
FR-4.6
Read receiptsMarking a conversation read is broadcast to the other members. In groups the sender can see which specific members have read a message.
Shipped
FR-4.7
Search within a conversationKeyword search over the caller's visible history, with matches highlighted and a jump back to position.
Shipped
FR-4.8
Word filteringA configurable list blocks matching messages at send time, including simple obfuscation.
Shipped
FR-4.9
Conversation list updates liveAn incoming message should update the list's preview, timestamp and unread badge without a reload. Currently the open conversation updates in real time but the list does not.
Defect

4.5 Media

FR-5.1
Images, video and files50 MB ceiling. Type is validated from content, not from the file name or the declared type.
Shipped
FR-5.2
ThumbnailsGenerated on upload. Pixel dimensions are bounded before decoding so a compression bomb cannot exhaust memory.
Shipped
FR-5.3
Only http and https media openEvery client keeps a scheme allowlist, independently of the server's check at send time.
Shipped
FR-5.4
Media requires authorisation to fetchUploaded files are currently served to anyone holding the URL. See OI-1.
Not met

4.6 Real-time delivery & notifications

FR-6.1
Authenticated socketThe connection authenticates with its first frame, within a ten-second window. The token is never placed in the URL.
Shipped
FR-6.2
Socket resource limitsPer-connection read ceiling, read deadline with 30-second keepalive pings, an unauthenticated-connection cap, and an origin allowlist.
Shipped
FR-6.3
Delivery is scoped to membersA message event reaches conversation members only — never every connected client.
Shipped
FR-6.4
Push only when it helpsRecipients who are connected, or who have muted the conversation, are skipped. Delivery attempts and provider receipts are both recorded.
Shipped
FR-6.5
Live provider credentialsFCM, APNs and vendor gateways are implemented and signed correctly; the deployment runs in mock mode until real credentials are supplied.
Config

4.7 Moderation console

FR-7.1
Three operator rolesAuditor reads, operator acts, superadmin manages operators. Enforced server-side on every route.
Shipped
FR-7.2
Destructive actions ask firstBans, role changes, report resolution and filter edits all require an explicit confirmation flag and are written to the audit log.
Shipped
FR-7.3
A ban takes effect immediatelyExisting tokens are revoked, not merely marked — a banned account is signed out everywhere at once.
Shipped
FR-7.4
Audit log with filters and exportBy actor, action, target, keyword and date range; exportable.
Shipped
FR-7.5
Operational visibilityReport queue, device registrations, push delivery and receipt history, client-reported metrics, and a risk-alert dashboard.
Shipped
FR-7.6
Console sign-in hardeningLockout after repeated failures, optional TOTP that fails closed when misconfigured, session binding to client address and agent, and an optional IP allowlist.
Shipped
FR-7.7
Single sign-onSigned-header SSO with replay protection and role matching. Implemented and verified; disabled by default and with no entry point in the console UI.
No UI
FR-7.8
Client-metric retentionThe purge endpoint exists but has no console control, so cleanup is API-only.
No UI

5Limits, timings & error codes

The boundary values worth testing at. Every figure here is read out of the shipped source, not out of an intention.

5.1 Input limits

FieldAcceptedRejected with
Account name3–32 characters of letters, digits, dot, underscore or dash400
Password8–128 characters400
Message textup to 8000 bytes — not characters, so a Chinese character costs three400
Quoted textthe same 8000 bytes400
Uploadup to 50 MB400
Image dimensions40 megapixels — a small file with enormous dimensions is refused400
JSON request body1 MB400

Uploads are accepted only for these extensions, and the bytes have to match the type the name claims:

.aac .bin .doc .docx .gif .jpeg .jpg .m4a .mov .mp3 .mp4 .ogg .pdf .png .ppt .pptx .txt .wav .webm .webp .xls .xlsx .zip

5.2 Rate limits

A sliding one-minute window per client IP address, not per account — so a whole test team behind one office address shares every bucket below. Worth knowing before reporting a mysterious 429.

ActionPer minute
Register5
Sign in10
Refresh a token30
Send a message120
Recall a message60
Upload media30
Push receipts120

Exceeding one returns 429 and too many requests. Separately, five failed sign-ins lock an account for fifteen minutes; that lock is on the account, so it follows the person to another device.

5.3 Timings

BehaviourValueWhat happens at the edge
Recall window120 sAfter two minutes recall is refused and the message stays as it was.
Socket authentication10 sA socket that has not authenticated within ten seconds of connecting is closed.
Heartbeat30 sThe server pings every thirty seconds; a client silent for ninety is dropped.
Socket frame size32 KBA larger frame closes the connection rather than truncating it.
Console session12 hThen the console drops back to its sign-in screen.
Concurrent sockets2000–4000Per deployment. Past the ceiling the upgrade is refused, not queued.

5.4 Responses

Every endpoint answers in the same envelope. code is 0 on success and 1 on failure; err and msg repeat code and message for an older client generation. The HTTP status carries the class of failure:

StatusMeansUsual cause
200SuccessBody is { "code": 0, "message": "ok", "data": … }. A list is always an array, never null.
400The request is wrongA limit from §5.1, a missing field, or a value the server will not take.
401Not signed inNo bearer token, or an expired one. Sign in again — on its own this is not a defect.
403Signed in, not allowedNot a member of the group, not an admin of it, or muted in it.
404No such thingAlso what routes that exist but are switched off in this deployment return, single sign-on among them.
429Rate limited§5.2.
500Server faultAlways worth reporting. Include the time — the server log is correlated by timestamp.

What makes a report actionable. The request (method, path, body), the whole response body including the envelope, which account it happened on, and the time. A screenshot of a client on its own rarely separates a client fault from a server one.

6Non-functional requirements

Constraints the implementation has to satisfy regardless of feature.

6.1 Security

NFR-1.1
No built-in credentialsNo default administrator password, no fallback console token, no seeded demo account. The static console token is refused unless explicitly enabled and at least 24 characters.
Shipped
NFR-1.2
Passwords are hashedbcrypt, with no plaintext fallback path anywhere in either storage backend.
Shipped
NFR-1.3
Browser hardening headersContent-Security-Policy without unsafe-inline for scripts, frame denial, MIME sniffing off, referrer policy, and HSTS — the last only over real TLS or from a proxy the deployment is configured to trust.
Shipped
NFR-1.4
Rate limiting that cannot be shedPer-endpoint limits keyed on the client address. Forwarded headers are honoured only where a proxy is configured, so the key cannot be forged.
Shipped
NFR-1.5
Bounded request bodies1 MB for JSON, separately bounded for uploads. Oversized requests are rejected rather than buffered.
Shipped
NFR-1.6
Signed server-to-server callsPush webhooks and receipts verify an HMAC signature and fail closed when no secret is configured.
Shipped

6.2 Reliability & performance

NFR-2.1
One socket writerAll writes to a connection go through a single goroutine. Concurrent writes previously crashed the whole process; a 25-second concurrent-write soak now runs clean.
Shipped
NFR-2.2
A handler panic cannot take the server downRecovery middleware wraps the router.
Shipped
NFR-2.3
Durable storageData must survive a restart. The persistent backend is implemented and tested against MySQL, MongoDB and Redis, but the current hosts run the in-memory backend. See OI-2.
Not met
NFR-2.4
Empty collections serialise as listsEvery list endpoint returns [], never null — a null once surfaced in the Android client as an error banner reading "ok".
Shipped

6.3 Compatibility

NFR-3.1
Android 8.0 and laterTargets API 35; release builds are minified and refuse cleartext traffic.
Shipped
NFR-3.2
Legacy client compatibilityFour aliases keep the previous generation of clients working, accepting both the old and new field names and both JSON and form-encoded bodies.
Shipped
NFR-3.3
No horizontal scrollingWeb layouts verified by measurement at 320, 390, 768 and 1440 pixels.
Shipped

7Known defects — do not re-report

Every one of these is already logged. Reproducing one is not a finding — please skip past them and spend the time on what is not listed here. Ordered by what would block a public launch.

Uploaded media is served without authorisation OI-1 · blocker

Anyone with the URL can fetch a file from a private conversation. File names are random, so the URLs are unguessable but not secret — and they travel to every member of a conversation. A complete fix needs authorisation at fetch time plus a file-to-conversation index; a partial fix that only obscures the URL would look like a fix without being one, so nothing has been changed yet.

Deployments run the in-memory backend OI-2 · blocker

A restart clears every account and message. The persistent backend is implemented and tested, but MongoDB refuses to start on the current hosts' kernel version, and no packaged release yet supports it. Resolution is a host with an older kernel, a managed MongoDB, or an upstream release — not a code change.

The conversation list does not update live OI-3 · high

Incoming messages update the open conversation but not the list's preview, timestamp or unread badge. Affects Android and the web clients, which share the behaviour. Tracked as FR-4.9.

iOS has no automated tests OI-4 · medium

Eleven source files, no test target, and no continuous-integration job. Android has twelve unit tests but the build pipeline does not execute them.

Cross-client checks are not automated OI-5 · medium

Roughly 260 browser and end-to-end assertions exist and pass, but they are run by hand. Until they run on every change they protect nothing.

The landing page’s legal links are dead OI-7 · low

youxin.chat links to /legal/privacy and /legal/terms; both return 404. The documents themselves exist in the repository but nothing serves them at those paths. Confirmed against the live site on 7 September 2026.

A conversation with oneself can be created OI-6 · low

Blocking yourself and adding yourself as a contact are both refused; opening a conversation with yourself is not. No client offers it, so it looks like a missing guard rather than a feature. Behaviour is pinned by a test either way.

8What is already covered automatically

Ground that machine checks hold, so manual effort can go elsewhere. None of it runs on every change yet — that is OI-5.

8.1 Server tests

117 Go test functions. The largest groups are the HTTP layer (43), storage (32) and the console (14). Within storage, 61 of those cases are a single behavioural suite run against both storage implementations — the in-memory one these deployments use and the database-backed one — so the two cannot quietly disagree. Writing that suite is what turned up the blocking, session-revocation and form-decoding defects already fixed in this build.

8.2 End-to-end and browser checks

Roughly 263 assertions, run by hand against a live deployment:

HarnessAssertionsCovers
endpoint_sweep92Every member and console route, with its authorisation rules.
qa_suite78The main user journeys, end to end.
web_group_check25Group management in the desktop web client.
qa_retest21Defects fixed earlier, held fixed.
audit_probe15Security boundaries: tokens, headers, forged proxy headers.
web_h5_check12The phone web layout.
web_member_view11What a plain member may and may not see.
admin_ui_check9The console shell and its session handling.

8.3 Where manual testing pays

  • iOS. No automated coverage at all. Everything there is unverified until a person runs it.
  • Android on real hardware. The unit tests never touch the interface, and nothing exercises notifications, backgrounding, or losing the network mid-send.
  • Two devices at once. Read receipts, unread counts and live delivery only mean anything with two sessions open, which no harness arranges.
  • Media that is awkward rather than invalid. A 49 MB file; a file whose extension lies about its contents; a photo straight off a phone camera at full resolution.
  • Anything visual. Layout, truncation, long display names, right-to-left text, dark mode.

9Goals & non-goals

What this release is trying to be — and what it deliberately is not.

Goals

  • One conversation, every device. Unread state, read receipts and history stay consistent whether a person opens the phone app or a browser.
  • Messages arrive now, not on refresh. A persistent socket with automatic reconnection; push notification when the socket is gone.
  • Groups people can actually run. Announcements, admins, mutes, invitations and removals — not just a shared thread.
  • An operator can answer for the platform. Every moderation action is attributable, reversible where it should be, and written to an audit log.
  • Safe by default. No built-in credential, no fallback token, nothing that only works because a setting was forgotten.

Non-goals for this release

  • Voice and video calling.
  • End-to-end encryption. Transport is TLS; the server can read message content, which is what the moderation console depends on.
  • Federation or self-hosting by third parties.
  • Threads, reactions, scheduled messages, message editing.
  • Desktop applications. The desktop experience is the web client.

10Users

Three audiences, three different jobs.

WhoWhat they come to doWhat failure looks like
Member Reach one person or a small group quickly; send a photo or a file; know whether it was seen. A message that silently didn't send, or a conversation list that isn't current.
Group owner Set the topic, promote a helper, quiet a disruptive member, remove someone, wind the group up. Having to ask an administrator to do something they should be able to do themselves.
Operator Work a report queue, ban an account, tune the word filter, and later explain what was done and why. An action with no record of who took it.

11Architecture

One backend, five front ends, two interchangeable storage backends.

ComponentBuilt withNotes
API serverGo 1.22Standard-library routing, gorilla/websocket. 93 routes plus /v1/ws.
Push gatewayGo 1.22Separate process. Talks to FCM, APNs and vendor endpoints; posts signed receipts back.
AndroidKotlin · ComposeMaterial 3, OkHttp, Coil, kotlinx.serialization.
iOSSwift · SwiftUIURLSession, native WebSocket.
Web clientsVanilla JSOne implementation, two layouts. No framework, no build step.
ConsoleVanilla JSSeparate application; delegated event handling so the CSP needs no inline-script exception.
StorageMySQL · MongoDB · RedisRelational for accounts and structure, document store for messages, Redis for sessions. An in-memory implementation of the same interface exists for test and preview.

Both storage backends implement one repository interface of 92 methods and are held to a single shared behavioural test suite, so a change verified against the in-memory implementation cannot silently diverge in the one production runs.

12Deployment

How the current environment is arranged.

HostServes
youxin.chatMarketing site and Android download
api.youxin.chatAPI and WebSocket
h5.youxin.chatWeb client, phone layout
pc.youxin.chatWeb client, desktop layout
admin.youxin.chatModeration console

Of these, youxin.chat and api.youxin.chat resolve today; the three client hostnames do not yet, which is why §2 gives the path form instead.

Each hostname is proxied to the same backend, so every web client is same-origin with the API it calls and no cross-origin permission is required. The API binds to the loopback interface only; the reverse proxy is the sole public listener, which is what makes trusting its forwarded client address safe.

Shared host. This machine also runs unrelated services. Each project owns exactly one reverse-proxy configuration file and no project rewrites the shared entry point — a convention adopted after a deployment script overwrote it and took a neighbouring site offline.

13Deferred

Considered and consciously postponed.

  • Schema migrations. Tables are created inline at start-up. Workable now; unworkable once the schema evolves across running instances.
  • Field-name consistency. Several endpoints accept two names for the same field, kept for the previous client generation. Harmless but a reliable source of integration mistakes — a reference table should ship with the API documentation.
  • Registration control. Anyone can create an account. An invitation code or approval step is needed before an environment holds real users.
  • Web unit tests. The pure functions in the web client — row grouping, highlight escaping, URL scheme checks — are testable in isolation and currently covered only end to end.

14Appendix

14.1 API surface

GroupPrefixRoutes
Authentication & account/v1/auth · /v1/account9
Contacts & safety/v1/friends · /v1/blocks · /v1/reports10
Conversations & groups/v1/conversations · /v1/groups18
Messaging/v1/messages6
Media, devices, metrics/v1/media · /v1/devices · /v1/client3
Real time/v1/ws1
Moderation console/admin/api29
Legacy aliases/im4

14.2 Terms

TermMeaning
ConversationA thread. Either 1:1, whose identifier is derived from the two participant ids, or a group with its own identifier.
SequenceA per-conversation counter on every message. The basis for ordering, resumption and read state.
RecallWithdrawing a sent message within two minutes. The message remains in history as a marker.
MuteTwo unrelated things: a person silencing a conversation for themselves, and a group owner preventing a member from sending.
OperatorA console account. Distinct from a member account; the two credential stores are separate.