Batch 5–9 QA Report
Build Results
| Check |
Result |
Notes |
cargo check --workspace |
✅ PASS |
All 10 crates compile cleanly |
cargo test --workspace |
✅ PASS |
0 tests exist; 0 failures. Note: test coverage is zero across the workspace |
cargo clippy --workspace -- -D warnings |
✅ PASS |
Fixed multiple lint issues during this run |
cd frontend && npm run build |
✅ PASS |
Built in 2.67s. Chunk size warning (>500KB) is non-blocking |
Lint Fixes Applied
cgcx-config: manual_range_contains → used !(MIN..=MAX).contains(&chunk)
cgcx-db: explicit_auto_deref → &mut conn; too_many_arguments on ForwardRepo::insert
cgcx-file-pipeline: collapsible_else_if (2×), clone_on_copy on Header
cgcx-bot: useless_vec, redundant_closure (auto-fixed), collapsible_else_if, manual_strip, too_many_arguments on propagate_punishment
cgcx-server: redundant_closure (auto-fixed), collapsible_else_if (auto-fixed), match equality (auto-fixed)
Regression Checklist
1. Review Buttons Work (approve, ignore, ban, blacklist, ban+blacklist)
| Item |
Status |
Evidence |
[ Approve ] callback |
✅ |
main.rs:1910 — sets password hash, forwards media to destination, DMs user, edits review message to [ APPROVED ] |
[ Ignore ] callback |
✅ |
main.rs:2067 — DMs user rejection, edits review message to [ IGNORED ] |
[ Blackl. ] callback |
✅ |
main.rs:2079 — adds user to forward blacklist, edits review message to [ BLACKLISTED ] |
[ Ban ] callback |
✅ |
main.rs:2087 — bans user in destination + review groups, inserts punishments, edits review message to [ BANNED ] |
[ Ban/BL u. ] callback |
✅ |
main.rs:2102 — bans + blacklists, edits review message to [ BAN/BL ] |
| Admin permission gate |
✅ |
main.rs:1903 — checks is_admin_in_chat for review group before processing action |
Risk: None of these flows have unit/integration tests.
2. GLOBAL_BAN Config Propagates Punishments When True
| Item |
Status |
Evidence |
| Config field present |
✅ |
GroupsConfig.global_ban: bool with default_global_ban() -> false |
| Propagation logic |
✅ |
propagate_punishment() in cgcx-bot/src/main.rs:2253 — early returns if !global_ban |
| Target chats |
✅ |
Admin groups + review groups + all active forward destination chats |
| Bot admin check |
✅ |
Skips chats where the bot is not an admin (is_admin) |
| Supported actions |
✅ |
ban, mute, kick with duration propagation |
| DB recording |
✅ |
Propagated punishments are inserted into punishments table per chat |
3. Author Visibility Toggle Works During Upload
| Item |
Status |
Evidence |
| Upload option exists |
✅ |
UploadOptions.show_author: bool, default true |
| Bot toggle callback |
✅ |
"v1:opt:toggle_author" → flips show_author, refreshes options message |
| Stored in DB |
✅ |
contents.show_author INTEGER NOT NULL DEFAULT 1 (migration 005) |
| Respected in upload result |
✅ |
main.rs:1346 — author_text only shown when options.show_author is true |
| Respected in forward post |
✅ |
main.rs:1937 — author_line respects content.show_author |
4. Metadata (date, size, author) Displays Correctly on View Page
| Item |
Status |
Evidence |
| Date |
✅ |
new Date(metadata.created_at).toLocaleString() in ViewContent.svelte:121 |
| Size |
✅ |
formatSize(metadata.total_size) in ViewContent.svelte:122 |
| Author (visible) |
✅ |
metadata.author rendered as @username [user_id] with Telegram link when show_author=true |
| Author (hidden) |
✅ |
Server returns author: null when show_author=false (main.rs:534) |
| Server-side gating |
✅ |
get_metadata only resolves AuthorInfo when content.show_author is true |
| View count / max views |
✅ |
metadata.current_views / metadata.max_views displayed conditionally |
5. Deduplication Reuses Existing Encrypted Files for Identical Uploads
| Item |
Status |
Evidence |
| Hashing |
✅ |
blake3::Hasher computes plaintext_hash during ingestion |
| Reuse logic |
✅ |
FilePipeline::ingest_file checks find_active_by_plaintext_hash (line 138) |
| Ref count increment |
✅ |
increment_ref_count(&existing.content_id, existing.file_index) called |
| New entry created |
✅ |
A new ContentFile row is inserted pointing to the existing stored_path |
| Temp file dropped |
✅ |
drop(named_temp) on dedup path avoids writing duplicate ciphertext |
| Migration present |
✅ |
006_dedup.sql adds plaintext_hash and ref_count columns |
6. Hash Blacklist Blocks Re-Uploads of Banned Content
| Item |
Status |
Evidence |
| Blacklist table |
✅ |
hash_blacklist table created by migration 007 |
| Check before store |
✅ |
HashBlacklistRepo::contains(hash_bytes) called in ingest_file before dedup (line 132) |
| Error type |
✅ |
Returns CgcxError::BlockedHash when blacklisted |
| Moderator API |
✅ |
HashBlacklistRepo::insert(hash, reason) available for adding entries |
7. Username Changes Are Logged to Configured JSON File
| Item |
Status |
Evidence |
| Config path |
✅ |
Config.uname_changes_path, default "data/uname_changes.json" |
| Tracking trigger |
✅ |
UserRepo::ensure_exists logs when old_username != new_username |
| Log format |
✅ |
JSON line per change: {timestamp, user_id, old_username, new_username, chat_id} |
| File mode |
✅ |
OpenOptions::new().create(true).append(true) |
| Called on every interaction |
✅ |
Bot calls user_repo.ensure_exists(..., Some(&uname_changes_path)) on messages and callbacks |
Blockers / Risks
-
Zero Test Coverage
- No unit or integration tests exist in any crate. All verification above is static code review.
- Recommendation: Add at least integration tests for
FilePipeline::ingest_file (dedup + blacklist paths) and the server metadata/password flows.
-
View Count Increment Location (Minor)
serve_file and serve_raw_file both increment views. A HEAD request or 304 Not Modified response correctly skips the increment.
- Range requests (
is_range) also skip increment. This is intentional but worth noting.
-
Chunk Size Warning (Non-blocking)
- Frontend build warns about a >500KB JS chunk. This is a performance consideration, not a functional blocker.
-
Clippy Cleanliness
- Workspace now passes
clippy -- -D warnings. This was not true before this QA run.
Summary
All requested features (review buttons, GLOBAL_BAN propagation, author toggle, metadata display, deduplication, hash blacklist, username tracking) are implemented and appear correct based on static analysis. The workspace compiles, tests pass (trivially), clippy is clean, and the frontend builds successfully. The primary risk is the complete absence of automated tests.