Skip to content

chore: property-based testing infrastructure — arbitraries module + conventions - #121

Merged
Bonobo791 merged 2 commits into
devfrom
pbt-infra
Aug 6, 2026
Merged

Bonobo791 merged 2 commits into
devfrom
pbt-infra

Conversation

@Bonobo791

@Bonobo791 Bonobo791 commented Aug 6, 2026 •

Copy link
Copy Markdown
Collaborator

User description

Summary

Foundation for fast-check property-based testing (skill: .agents/skills-src/fast-check-testing):

  • src/lib/server/testarbitraries.ts — shared arbitraries: ids/channel ids, ISO timestamps (past/future relative to an injected now), comment text (in-range, over the 500-char storage limit, hostile Unicode), schema row arbitraries honoring the channels CHECK, an org entity graph (fc.entityGraph with resolved links), YouTube commentThreads.list payloads (valid + malformed-item mixes for I1 fuzzing), recheck-verified safe/unsafe regex fragments, and pbtNumRuns() (FC_NUM_RUNS env, default 100, throws on garbage).
  • src/lib/server/testarbitraries.test.ts — 27 meta-tests pinning every construction contract; payload arbitraries are exercised through the real fetchNewComments parser (fetch stubbed), and malformItem is asserted per malformation kind.
  • src/lib/server/testdb.ts — extract + export wipeTables so PBT tests can wipe a subset of tables between properties.
  • docs/property-testing.md — conventions doc: block-bodied predicates (fast-check treats a returned vitest Assertion object as failure), deliberate-break checks per property, seeded sampling, batch log.
  • AGENTS.md — pointer to the fast-check-testing skill + conventions doc.

Design decisions

  • The module lives inside the Stryker mutate scope (src/lib/server/, non-test): wrong test infrastructure must be caught like wrong production code. Its mutation score is part of this PR's proof.
  • fc.entityGraph can't infer field types, and explicit generics hit a TS circular-reference error in fast-check's recursive EntityGraphValue; the value type is asserted to hand-written OrgGraph interfaces (as unknown as, with a comment) and the runtime shape is pinned by the orgGraphArb links resolve meta-test.
  • Properties complement example tests and never count toward Stryker kill claims.

Verification

  • npm run test: 977/977 green (949 + 27 meta + 1 wipeTables)
  • npm run check: 0 errors / 0 warnings
  • npm run build: OK
  • Scoped Stryker (--ignoreStatic --concurrency 1, fresh cache) on testarbitraries.ts + testdb.ts: 100% — 49+27 killed, 0 survived, 0 errors, 1 justified exclusion (response kind string literal never read by the parser — // Stryker disable comment in place)

Next

Batch P1 (branch pbt-batch-1 after this merges): crypto round-trip/tamper, oauthState round-trip/bounds, session uniqueness + sliding-expiry cap.


CodeAnt-AI Description

Add property-based testing support for validating comment moderation and tenant data behavior

What Changed

  • Adds reusable random test data for users, organizations, channels, comments, rules, tenant relationships, timestamps, hostile text, and YouTube comment responses
  • Verifies that valid YouTube comments are processed, malformed items are skipped without aborting a batch, and safe or unsafe regex patterns are handled correctly
  • Adds configurable property-test run counts through FC_NUM_RUNS, with invalid values reported instead of silently ignored
  • Adds database table cleanup for each generated test case to prevent state from leaking between property runs
  • Documents conventions for deterministic, reproducible property tests and their relationship to mutation testing

Impact

✅ Malformed YouTube comments no longer stop batch parsing tests
✅ Repeatable tenant and moderation data validation
✅ Clear configuration errors for property-test runs

💡 Usage Guide

Checking Your Pull Request

Every time you make a pull request, our system automatically looks through it. We check for security issues, mistakes in how you're setting up your infrastructure, and common code problems. We do this to make sure your changes are solid and won't cause any trouble later.

Talking to CodeAnt AI

Got a question or need a hand with something in your pull request? You can easily get in touch with CodeAnt AI right here. Just type the following in a comment on your pull request, and replace "Your question here" with whatever you want to ask:

@codeant-ai ask: Your question here

This lets you have a chat with CodeAnt AI about your pull request, making it easier to understand and improve your code.

Example

@codeant-ai ask: Can you suggest a safer alternative to storing this secret?

Preserve Org Learnings with CodeAnt

You can record team preferences so CodeAnt AI applies them in future reviews. Reply directly to the specific CodeAnt AI suggestion (in the same thread) and replace "Your feedback here" with your input:

@codeant-ai: Your feedback here

This helps CodeAnt AI learn and adapt to your team's coding style and standards.

Example

@codeant-ai: Do not flag unused imports.

Retrigger review

Ask CodeAnt AI to review the PR again, by typing:

@codeant-ai: review

Check Your Repository Health

To analyze the health of your code repository, visit our dashboard at https://app.codeant.ai. This tool helps you identify potential issues and areas for improvement in your codebase, ensuring your repository maintains high standards of code health.

…onventions

- src/lib/server/testarbitraries.ts: shared fast-check arbitraries
  (ids, ISO timestamps past/future, comment text incl. over-limit and
  hostile Unicode, schema row arbitraries, org entity graph, YouTube
  commentThreads payloads with malformations, recheck-verified safe/unsafe
  regex fragments) plus pbtNumRuns() (FC_NUM_RUNS env, default 100)
- src/lib/server/testarbitraries.test.ts: 27 meta-tests pinning every
  construction contract, including payload arbitraries exercised through
  the real fetchNewComments parser
- src/lib/server/testdb.ts: extract + export wipeTables (PBT tests wipe
  subsets of tables between properties)
- docs/property-testing.md: conventions doc (predicate Assertion gotcha,
  deliberate-break checks, batch log)
- AGENTS.md: pointer to the fast-check-testing skill and conventions doc

Scoped Stryker (--ignoreStatic) on testarbitraries.ts + testdb.ts: 100% —
51+27 killed, 0 survived, 1 justified exclusion (response kind string
never read by the parser).
@cla-bot cla-bot Bot added the cla-signed label Aug 6, 2026
@codeant-ai

codeant-ai Bot commented Aug 6, 2026 •

Copy link
Copy Markdown

🤖 CodeAnt AI — Review Status

Status Commit Started (UTC) Finished (UTC)
✅ Incremental review completed d57382f Aug 06, 2026 · 12:11 12:14
✅ Reviewed your PR ae3a606 Aug 06, 2026 · 11:57 12:01

@codeant-ai

codeant-ai Bot commented Aug 6, 2026

Copy link
Copy Markdown

Thanks for using CodeAnt! 🎉

We're free for open-source projects. if you're enjoying it, help us grow by sharing.

Share on X ·
Reddit ·
LinkedIn

@netlify

netlify Bot commented Aug 6, 2026 •

Copy link
Copy Markdown

✅ Deploy Preview for moderaty ready!

Name Link
🔨 Latest commit ae3a606
🔍 Latest deploy log https://app.netlify.com/projects/moderaty/deploys/6a7476a9eca4130008c3b06d
😎 Deploy Preview https://deploy-preview-121--moderaty.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.
Lighthouse
Lighthouse
1 paths audited
Performance: 91
Accessibility: 97
Best Practices: 100
SEO: 100
PWA: -
View the detailed breakdown and full score reports
🤖 Make changes Run an agent on this branch

To edit notification comments on pull requests, go to your Netlify project configuration.

@coderabbitai

coderabbitai Bot commented Aug 6, 2026 •

Copy link
Copy Markdown
Contributor

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 9d9c099a-4f30-4bb7-bc40-a42623b2c3d5

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@codeant-ai codeant-ai Bot added the size:XL This PR changes 500-999 lines, ignoring generated files label Aug 6, 2026
@codeant-ai

codeant-ai Bot commented Aug 6, 2026

Copy link
Copy Markdown

Sequence Diagram

This PR adds shared fast-check generators and uses them to exercise the real YouTube comment parser with valid and malformed payloads. It also provides table wiping so stateful properties can start each generated run from a clean database.

sequenceDiagram
    participant PropertyTest
    participant Arbitraries
    participant YouTubeParser
    participant TestDatabase

    PropertyTest->>Arbitraries: Generate valid or malformed payload
    PropertyTest->>YouTubeParser: Parse generated response
    YouTubeParser-->>PropertyTest: Process valid items and skip malformed items
    PropertyTest->>TestDatabase: Wipe tables before stateful run
    TestDatabase-->>PropertyTest: Return clean test state
Loading

Generated by CodeAnt AI

@codeant-ai

codeant-ai Bot commented Aug 6, 2026 •

Copy link
Copy Markdown

🏁 CodeAnt Quality Gate Results

Commit: d57382fd
Scan Time: 2026-08-06 12:14:44 UTC

✅ Overall Status: PASSED

Quality Gate Details

Quality Gate Status Details
Secrets ✅ PASSED 0 secrets found
Duplicate Code ✅ PASSED 0.0% duplicated
SAST ✅ PASSED No security issues
Bugs ✅ PASSED Rating S: No bugs
IAC ✅ PASSED No IAC issues

View Full Results

@amazon-q-developer amazon-q-developer Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This PR establishes solid property-based testing infrastructure with comprehensive arbitraries and meta-tests. The implementation is thorough with 977/977 passing tests and 100% Stryker mutation score.

Critical Issue Identified:

The wipeTables function uses unvalidated string interpolation for SQL DELETE statements, creating an SQL injection vulnerability pattern (CWE-89). While this is test infrastructure, it establishes an insecure pattern that could be replicated in production code. Add table name validation against an allowlist before the SQL construction.

Once this security issue is resolved, the PR will be ready for merge. The test coverage, documentation, and mutation testing verification are exemplary.


You can now have the agent implement changes and create commits directly on your pull request's source branch. Simply comment with /q followed by your request in natural language to ask the agent to make changes.

Comment thread src/lib/server/testdb.ts
* inside one test, so state must be wiped inside the predicate too).
*/
export async function wipeTables(tables: string[]): Promise<void> {
await testDb().client.batch(tables.map((table) => `DELETE FROM ${table}`));

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛑 SQL Injection Vulnerability: Constructing SQL with unvalidated string interpolation creates injection risk. The table parameter is directly interpolated into the DELETE query without validation or parameterization.1

While this is test code, establishing secure patterns in test infrastructure prevents similar vulnerabilities from appearing in production code through copy-paste. Add table name validation against a known allowlist or use parameterized queries if the database client supports them.

Suggested change
await testDb().client.batch(tables.map((table) => `DELETE FROM ${table}`));
export async function wipeTables(tables: string[]): Promise<void> {
// Validate table names against known schema to prevent SQL injection
const allowedTables = ['users', 'sessions', 'channels', 'rules', 'comments', 'audit_log', 'moderation_actions', 'consents', 'organizations', 'memberships', 'invites'];
for (const table of tables) {
if (!allowedTables.includes(table)) {
throw new Error(`Invalid table name: ${table}. Allowed tables: ${allowedTables.join(', ')}`);
}
}
await testDb().client.batch(tables.map((table) => `DELETE FROM ${table}`));
}

Footnotes

  1. CWE-89: SQL Injection - https://cwe.mitre.org/data/definitions/89.html ↩

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Valid — fixed on dev in 3f2aa84 (this PR merged before triage; fixes land there). wipeTables now validates every name against an allowlist derived from the Drizzle schema (plus __drizzle_migrations, which drizzle-kit owns but the migration guard reads) and throws wipeTables: unknown table "…" on anything else. Failing test first: wipeTables rejects tables outside the app schema loudly — it caught a real silent DELETE FROM sqlite_sequence before the fix.

@qodo-code-review

Copy link
Copy Markdown

PR Summary by Qodo

Add fast-check PBT arbitraries module, meta-tests, and repo conventions

🧪 Tests 📝 Documentation 🕐 40+ Minutes

Grey Divider

AI Description

• Introduce shared fast-check arbitraries for schema rows, time, text, and YouTube payload fuzzing.
• Add meta-tests that pin generator contracts and exercise payloads through the real parser.
• Document PBT conventions and expose wipeTables for per-property-run DB freshness.
Diagram

graph TD
  Env{{"FC_NUM_RUNS"}} --> Arb["testarbitraries.ts"] --> FC{{"fast-check"}}
  PBT("*.pbt.test.ts") --> Arb
  PBT --> DB["testdb.ts wipeTables"]
  Meta("testarbitraries.test.ts") --> Arb --> YT["youtube.ts parser"]
  Agents["AGENTS.md"] --> Doc["property-testing.md"] --> Arb
  subgraph Legend
    direction LR
    _t("Test") ~~~ _m["Module/File"] ~~~ _e{{"External"}}
  end
Loading
High-Level Assessment

The following are alternative approaches to this PR:

1. Keep arbitraries outside Stryker mutate scope (tests-only folder)
  • ➕ Less risk of affecting mutation score for non-production code
  • ➕ Clearer separation of production vs test infrastructure
  • ➖ Weakens confidence in generator correctness (arbitrary bugs can silently weaken every property)
  • ➖ Relies on properties to “test the generator,” which is often ineffective
2. Derive arbitraries from schemas via a generator library (e.g., zod + zod-fast-check)
  • ➕ Less hand-maintained boilerplate as schema evolves
  • ➕ Potentially more uniform coverage across shapes
  • ➖ Repo intentionally avoids zod; would add a new schema layer/dependency
  • ➖ Harder to encode domain-specific constraints (CHECKs, hostile Unicode mix, deliberate malformed payloads)
3. Avoid global fast-check configuration; pass numRuns per fc.assert/property
  • ➕ Eliminates module-import side effects
  • ➕ Allows per-suite/per-test tuning without env coupling
  • ➖ More repetitive boilerplate across properties
  • ➖ Easier for tests to accidentally diverge from standard run-count conventions

Recommendation: The PR’s approach is well-chosen for this repo: hand-built arbitraries encode domain constraints and hostile inputs, and the meta-tests (plus keeping the module in Stryker scope) are a strong guardrail against silently weakened generators. Global numRuns configuration via import is acceptable given the clear conventions and the loud failure on invalid FC_NUM_RUNS.

Files changed (6) +967 / -3

Enhancement (1) +10 / -1
testdb.tsExtract and export wipeTables; reuse in setupTestDb hooks +10/-1

Extract and export wipeTables; reuse in setupTestDb hooks

• Introduces wipeTables(tables) to delete all rows from a specified set of tables, enabling per-property-run cleanup inside a single vitest test. Refactors setupTestDb to call wipeTables instead of inlining DELETE batches.

src/lib/server/testdb.ts

Tests (2) +385 / -2
testarbitraries.test.tsAdd meta-tests pinning shared arbitrary contracts via real parsers +376/-0

Add meta-tests pinning shared arbitrary contracts via real parsers

• Adds 27 meta-tests that validate every arbitrary’s shape/constraints, including time/id/text and schema-row invariants. Exercises generated YouTube payloads through the real fetchNewComments parser with stubbed fetch, and asserts malformation behavior per kind.

src/lib/server/testarbitraries.test.ts

testdb.test.tsAdd regression test for wipeTables to support per-property freshness +9/-2

Add regression test for wipeTables to support per-property freshness

• Extends the test DB harness self-tests by verifying wipeTables deletes rows for specified tables. Updates imports accordingly to include users and wipeTables.

src/lib/server/testdb.test.ts

Documentation (2) +114 / -0
AGENTS.mdDocument where fast-check PBT conventions and arbitraries live +5/-0

Document where fast-check PBT conventions and arbitraries live

• Adds a short section pointing contributors/agents to the fast-check-testing skill, the new PBT conventions doc, and the shared arbitraries module. Clarifies that properties complement example tests and do not count toward Stryker kill claims.

AGENTS.md

property-testing.mdAdd fast-check property-testing conventions and batch log +109/-0

Add fast-check property-testing conventions and batch log

• Introduces repo-specific guidance for writing deterministic, replayable fast-check properties (predicate return-value gotcha, per-run DB freshness, avoiding heavy filtering, seed/path replay). Documents how PBT relates to Stryker and includes an initial batch log/roadmap.

docs/property-testing.md

Other (1) +458 / -0
testarbitraries.tsIntroduce shared fast-check arbitraries + global run-count configuration +458/-0

Introduce shared fast-check arbitraries + global run-count configuration

• Adds a shared arbitraries module for ids, timestamps, hostile/over-limit comment text, drizzle-like row shapes, an org entity graph with resolved links, YouTube commentThreads responses (valid/mixed) with explicit malformations, and safe/unsafe regex patterns. Configures fast-check globally from FC_NUM_RUNS (default 100) and fails loudly on invalid values.

src/lib/server/testarbitraries.ts

@codacy-production

codacy-production Bot commented Aug 6, 2026 •

Copy link
Copy Markdown
Contributor

Not up to standards ⛔

NEW Get contextual insights on your PRs based on Codacy's metrics, along with PR and Jira context, without leaving GitHub. Enable AI reviewer
TIP This summary will be updated as you push new changes.

@codeant-ai

codeant-ai Bot commented Aug 6, 2026 •

Copy link
Copy Markdown

PR Code Suggestions ✨

Latest suggestions up to commit ae3a606
CategorySuggestion                                                                                                                                    SeverityGenerated at (UTC)
Api mismatch
Generated identifiers do not match the lengths used by production identifiers

The shared idArb generates eight-character IDs, but application-created user and
organization IDs are 32 hexadecimal characters and session/invite tokens are 64
characters. Properties using this arbitrary therefore do not exercise
production-shaped identifier lengths and can miss length-sensitive validation,
truncation, serialization, or collision behavior. Use separate arbitraries matching
each persisted identifier contract, or make the generic ID arbitrary 32 characters
where that is the intended contract.

src/lib/server/testarbitraries.ts [52]

Why it matters? 🤔
  • ⚠️ PBT identity values differ from production lengths.
  • ⚠️ Length-sensitive persistence properties can miss regressions.
  • ⚠️ Session and invite token properties need 64-character values.

Fix in Cursor Fix in VSCode Claude

(Use Cmd/Ctrl + Click for best experience)

Prompt for AI Agent 🤖
This is a comment left during a code review.

**Path:** src/lib/server/testarbitraries.ts
**Line:** 52:52
**Comment:**
	*Api Mismatch: The shared `idArb` generates eight-character IDs, but application-created user and organization IDs are 32 hexadecimal characters and session/invite tokens are 64 characters. Properties using this arbitrary therefore do not exercise production-shaped identifier lengths and can miss length-sensitive validation, truncation, serialization, or collision behavior. Use separate arbitraries matching each persisted identifier contract, or make the generic ID arbitrary 32 characters where that is the intended contract.

Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.
Once fix is implemented, also check other comments on the same PR, and ask user if the user wants to fix the rest of the comments as well. if said yes, then fetch all the comments validate the correctness and implement a minimal fix
Major2026-08-06 12:00

@qodo-code-review

Copy link
Copy Markdown

Code Review by Qodo

🐞 Bugs (2) 📘 Rule violations (1) 📜 Skill insights (0)

Grey Divider


Remediation recommended

1. Markdown license header not comment 📘 Rule violation § Compliance
Description
docs/property-testing.md starts with # headings, which are rendered Markdown, not a valid
comment-style AGPL header for a .md file. This violates the requirement that new comment-capable
files use the file’s native comment syntax for the license header.
Code

docs/property-testing.md[R1-4]

+# Moderaty — YouTube Comment Auto-Moderation Tool
+# Copyright (C) 2026 Andrew Philip Weilbacher
+#
+# This program is free software: you can redistribute it and/or modify
Relevance

●●● Strong

Repo enforces AGPL headers on new comment-capable files; likely will require proper Markdown comment
syntax.

PR-#23

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
PR Compliance ID 2401185 requires that each newly added comment-capable file (including .md) begin
with an AGPL header using that file type’s valid comment syntax. The added
docs/property-testing.md begins with Markdown headings (# ...) rather than an HTML comment
block, so the header is not in the file’s native comment syntax.

Rule 2401185: AGPL License Header in New Comment-Capable Files
docs/property-testing.md[1-18]
docs/e2e-verification.md[1-20]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`docs/property-testing.md` is a newly added `.md` file, but its AGPL header is written using Markdown headings (`# ...`) instead of Markdown/HTML comment syntax (`<!-- ... -->`). This makes the license header render as visible content and does not meet the “native comment syntax” requirement.

## Issue Context
Other repo docs use an HTML comment block for the AGPL header.

## Fix Focus Areas
- docs/property-testing.md[1-18]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


2. Unvalidated table identifiers 🐞 Bug ⚙ Maintainability
Description
wipeTables() interpolates caller-provided table names directly into SQL (DELETE FROM ${table}),
so a typo or malformed string can target the wrong table or produce invalid SQL and break test
isolation.
This is test-only infrastructure, but it is exported and intended to be called inside properties, so
hardening the API prevents subtle/irreproducible test failures.
Code

src/lib/server/testdb.ts[R49-51]

+export async function wipeTables(tables: string[]): Promise<void> {
+	await testDb().client.batch(tables.map((table) => `DELETE FROM ${table}`));
+}
Relevance

●●● Strong

Team often accepts input/parameter validation hardening; validating table identifiers avoids flaky
test infra failures.

PR-#2

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The new helper builds SQL by string interpolation with no validation, making table targeting
dependent on caller-provided raw strings.

src/lib/server/testdb.ts[49-51]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`wipeTables()` currently constructs SQL by interpolating raw table identifiers. This makes the helper easy to misuse (typos, unexpected strings) and can result in deleting unintended tables or failing with invalid SQL.

## Issue Context
This is test infrastructure, but it is exported and explicitly intended for use inside property predicates, so it should be hard to call incorrectly.

## Fix Focus Areas
- src/lib/server/testdb.ts[49-51]

## Suggested fix
- Introduce an explicit allowlist/type for table names (e.g. `const TEST_TABLES = [...] as const; export type TestTable = (typeof TEST_TABLES)[number];`).
- Change the signature to `wipeTables(tables: TestTable[])`.
- At runtime, validate each requested table is in the allowlist before building SQL.
- Optionally quote identifiers (`DELETE FROM "${table}"`) once table is allowlisted to avoid edge cases with reserved words.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


3. FK-unsafe wipe order 🐞 Bug ☼ Reliability
Description
wipeTables() executes deletes in the exact order provided by the caller, which can violate foreign
key constraints when a parent table is wiped before dependent rows.
For example, invites.created_by references users(id) without ON DELETE CASCADE, and the test
DB enables FK enforcement, so deleting users while invites still has rows will throw.
Code

src/lib/server/testdb.ts[R49-51]

+export async function wipeTables(tables: string[]): Promise<void> {
+	await testDb().client.batch(tables.map((table) => `DELETE FROM ${table}`));
+}
Relevance

●● Moderate

FK-safe wipe ordering/strategy is nontrivial; might be left to callers unless failures appear.

PR-#48

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
Foreign keys are explicitly enabled for the shared in-memory DB, and the invites table references
users via created_by without a cascade, so deleting users first can fail. wipeTables() does
not account for dependencies and just executes deletes in input order.

src/lib/server/testdb.ts[49-51]
src/lib/server/testdb.ts[109-112]
src/lib/server/testdb.ts[217-224]
src/lib/server/db/schema.ts[92-106]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`wipeTables()` deletes tables in caller-provided order. With foreign keys enabled in the in-memory DB, deleting tables in an unsafe order can raise constraint errors and make property tests fail in surprising ways.

## Issue Context
The test DB enables foreign key enforcement (`PRAGMA foreign_keys = ON`). The schema includes at least one non-cascading FK from `invites.created_by` to `users(id)`, so parent-first deletion is not always valid.

## Fix Focus Areas
- src/lib/server/testdb.ts[49-51]

## Suggested fix
- Internally sort the requested tables into a canonical child→parent deletion order before issuing deletes (even when wiping only a subset).
 - Example rank order (based on current DDL): `invites`, `memberships`, `consents`, `sessions`, `organizations`, `users` (and include other tables as needed).
- Keep the external API the same, but make it resilient to caller order.
- Add/extend a small unit test demonstrating that wiping `["users","invites"]` succeeds (internally reordered) when both tables contain rows.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

Context used
✅ Compliance rules (platform): 91 rules

To customize comments, go to the Qodo configuration screen, or learn more in the docs.

Qodo Logo

Comment thread docs/property-testing.md
Comment on lines +1 to +4
# Moderaty — YouTube Comment Auto-Moderation Tool
# Copyright (C) 2026 Andrew Philip Weilbacher
#
# This program is free software: you can redistribute it and/or modify

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remediation recommended

1. Markdown license header not comment 📘 Rule violation § Compliance

docs/property-testing.md starts with # headings, which are rendered Markdown, not a valid
comment-style AGPL header for a .md file. This violates the requirement that new comment-capable
files use the file’s native comment syntax for the license header.
Agent Prompt
## Issue description
`docs/property-testing.md` is a newly added `.md` file, but its AGPL header is written using Markdown headings (`# ...`) instead of Markdown/HTML comment syntax (`<!-- ... -->`). This makes the license header render as visible content and does not meet the “native comment syntax” requirement.

## Issue Context
Other repo docs use an HTML comment block for the AGPL header.

## Fix Focus Areas
- docs/property-testing.md[1-18]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Valid — fixed on dev in 3f2aa84. The header was bare # lines (rendered Markdown headings), not a comment; it now uses the <!-- … --> form matching the other files in docs/ (e2e-verification.md, tenancy-audit.md).

Comment thread src/lib/server/testdb.ts
Comment on lines +49 to +51
export async function wipeTables(tables: string[]): Promise<void> {
await testDb().client.batch(tables.map((table) => `DELETE FROM ${table}`));
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remediation recommended

2. Unvalidated table identifiers 🐞 Bug ⚙ Maintainability

wipeTables() interpolates caller-provided table names directly into SQL (DELETE FROM ${table}),
so a typo or malformed string can target the wrong table or produce invalid SQL and break test
isolation.
This is test-only infrastructure, but it is exported and intended to be called inside properties, so
hardening the API prevents subtle/irreproducible test failures.
Agent Prompt
## Issue description
`wipeTables()` currently constructs SQL by interpolating raw table identifiers. This makes the helper easy to misuse (typos, unexpected strings) and can result in deleting unintended tables or failing with invalid SQL.

## Issue Context
This is test infrastructure, but it is exported and explicitly intended for use inside property predicates, so it should be hard to call incorrectly.

## Fix Focus Areas
- src/lib/server/testdb.ts[49-51]

## Suggested fix
- Introduce an explicit allowlist/type for table names (e.g. `const TEST_TABLES = [...] as const; export type TestTable = (typeof TEST_TABLES)[number];`).
- Change the signature to `wipeTables(tables: TestTable[])`.
- At runtime, validate each requested table is in the allowlist before building SQL.
- Optionally quote identifiers (`DELETE FROM "${table}"`) once table is allowlisted to avoid edge cases with reserved words.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Valid — fixed on dev in 3f2aa84 (same fix as the amazon-q thread above): caller-provided names are now checked against a schema-derived allowlist before any SQL is built, so a typo or malformed string throws loudly instead of targeting the wrong table. Pinned by wipeTables rejects tables outside the app schema loudly in testdb.test.ts.

Comment thread src/lib/server/testdb.ts
Comment on lines +49 to +51
export async function wipeTables(tables: string[]): Promise<void> {
await testDb().client.batch(tables.map((table) => `DELETE FROM ${table}`));
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remediation recommended

3. Fk-unsafe wipe order 🐞 Bug ☼ Reliability

wipeTables() executes deletes in the exact order provided by the caller, which can violate foreign
key constraints when a parent table is wiped before dependent rows.
For example, invites.created_by references users(id) without ON DELETE CASCADE, and the test
DB enables FK enforcement, so deleting users while invites still has rows will throw.
Agent Prompt
## Issue description
`wipeTables()` deletes tables in caller-provided order. With foreign keys enabled in the in-memory DB, deleting tables in an unsafe order can raise constraint errors and make property tests fail in surprising ways.

## Issue Context
The test DB enables foreign key enforcement (`PRAGMA foreign_keys = ON`). The schema includes at least one non-cascading FK from `invites.created_by` to `users(id)`, so parent-first deletion is not always valid.

## Fix Focus Areas
- src/lib/server/testdb.ts[49-51]

## Suggested fix
- Internally sort the requested tables into a canonical child→parent deletion order before issuing deletes (even when wiping only a subset).
  - Example rank order (based on current DDL): `invites`, `memberships`, `consents`, `sessions`, `organizations`, `users` (and include other tables as needed).
- Keep the external API the same, but make it resilient to caller order.
- Add/extend a small unit test demonstrating that wiping `["users","invites"]` succeeds (internally reordered) when both tables contain rows.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Acknowledged + fixed preventively on dev in 3f2aa84. Not reproducible today — every FK in the schema is ON DELETE CASCADE, so caller order cannot currently violate anything — but the first non-cascade FK would have made wipes order-dependent. wipeTables now wraps the deletes in PRAGMA foreign_keys = OFF/ON via executeMultiple (verified empirically: libsql's batch() is transactional, which makes the pragma a no-op). Pinned by wipeTables suspends FK enforcement around the deletes, which asserts OFF precedes every DELETE and ON follows.

@Bonobo791

Copy link
Copy Markdown
Collaborator Author

Consolidation note (maintainer decision): to cut Netlify production-deploy credit spend, this repo is moving to a single-dev-branch model with one full-stack dev. This PR's base has been retargeted from main to dev. After #119 lands on dev, this branch will need a small conflict resolution in src/lib/server/testdb.ts and AGENTS.md — I'll handle that here before merging. Ships to production later in a batched dev → main merge.

@Bonobo791
Bonobo791 changed the base branch from main to dev August 6, 2026 12:10
@codeant-ai

codeant-ai Bot commented Aug 6, 2026

Copy link
Copy Markdown

Thanks for using CodeAnt! 🎉

We're free for open-source projects. if you're enjoying it, help us grow by sharing.

Share on X ·
Reddit ·
LinkedIn

@Bonobo791
Bonobo791 merged commit d9ca1cf into dev Aug 6, 2026
2 of 5 checks passed
@codeant-ai codeant-ai Bot added size:XL This PR changes 500-999 lines, ignoring generated files and removed size:XL This PR changes 500-999 lines, ignoring generated files labels Aug 6, 2026
@sonarqubecloud

sonarqubecloud Bot commented Aug 6, 2026

Copy link
Copy Markdown

@Bonobo791
Bonobo791 deleted the pbt-infra branch August 6, 2026 12:14
Bonobo791 added a commit that referenced this pull request Aug 6, 2026
@Bonobo791

Copy link
Copy Markdown
Collaborator Author

Triage — PR #121 bot review (mutation-testing engineer)

This PR merged into dev before triage finished; fixes landed on dev (3f2aa84) and in follow-up PR #124. Addressing each bot directly:

@qodo-code-review — all three findings valid, all fixed:

  1. Markdown license header not comment (docs/property-testing.md) — valid. The header was bare # lines (rendered headings); fixed in 3f2aa84 to the <!-- … --> form matching the other docs/ files.
  2. Unvalidated table identifiers (wipeTables) — valid. Fixed in 3f2aa84: names are validated against a schema-derived allowlist and anything else throws loudly. (Also covers @Amazon-Q-Developer's SQL-injection thread — same fix.)
  3. FK-unsafe wipe order — valid premise: invites.created_by → users.id (src/lib/server/db/schema.ts:102) is the one FK without ON DELETE CASCADE. Fixed preventively in 3f2aa84 by suspending FK enforcement around the deletes.

@Amazon-Q-Developer — your wipeTables SQL-interpolation flag was valid for a shared test API (typo/loud-failure risk, not a real injection vector in tests); fixed in 3f2aa84 via the allowlist.

@sonarqubecloud — your 5 MINOR issues were valid and behavior-preserving; fixed in PR #124: dedicated not.toBeNull() matcher (S5906) and String.raw for the four escaped regex fragments (S7780 ×4 — runtime strings are byte-identical, still pinned by the recheck meta-tests). Scoped Stryker re-run after the change: 100% (49 killed, 0 survived).

@CodeAnt-AI — your Api mismatch on idArb (8 vs 32/64 chars): assessed, not fixing the generic idArb. Nothing in the codebase is length-sensitive about entity ids — text primary keys, no length validation anywhere, and the repo's existing test helpers use short ids ('bare', 'new-user') throughout. The half of the finding that does matter is session/invite tokens (64-hex contract): Batch P1's session properties (pbt-batch-1) will generate 64-hex token values where the token itself is the value under test.

Codacy (codacy-production) — the "not up to standards" verdict is the 4 clones + complexity increase in testarbitraries.ts. Acknowledged, not fixing: the parallel fc.record constructions are deliberate (house rule: three similar lines beat a premature abstraction), and the complexity delta is the new module's size, not convoluted logic.

@coderabbitai — no review to triage; the review limit was reached for this PR. Flagging in case you want a manual @coderabbitai review on #124.

security/snyk — failing identically on #114/#118/#120 (all merged) and on this dependency-free diff, so it's a pre-existing repo-wide check state, not something #121 introduced. Leaving it to the maintainer.

Verification for #124: 1009/1009 tests, check 0/0, build OK, scoped Stryker 100% on testarbitraries.ts.

@coderabbitai

coderabbitai Bot commented Aug 6, 2026

Copy link
Copy Markdown
Contributor

Tip

For best results, initiate chat on the files or code changes.

@Bonobo791 Acknowledged. The triage clearly separates fixed findings, accepted design decisions, and the pre-existing Snyk failure.

Please request the manual review on PR #124 so the review applies to its follow-up diff and current dev state.


✏️ Learnings added
Learnt from: Bonobo791
URL: https://github.com/Bonobo791/Moderaty/pull/121

Timestamp: 2026-08-06T16:17:05.319Z
Learning: In this repository, generic entity IDs are text primary keys with no length-sensitive validation. Shared property-based test arbitrary `idArb` may generate short IDs. Existing test helpers also use short IDs such as `bare` and `new-user`.

Learnt from: Bonobo791
URL: https://github.com/Bonobo791/Moderaty/pull/121

Timestamp: 2026-08-06T16:17:05.319Z
Learning: In this repository, session and invite tokens have a 64-hex-character contract. Property-based tests for token behavior must generate 64-hex token values.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.

You are interacting with an AI system.

@qodo-code-review

Copy link
Copy Markdown

Triage — PR #121 bot review (mutation-testing engineer)

This PR merged into dev before triage finished; fixes landed on dev (3f2aa84) and in follow-up PR #124. Addressing each bot directly:

@qodo-code-review — all three findings valid, all fixed:

  1. Markdown license header not comment (docs/property-testing.md) — valid. The header was bare # lines (rendered headings); fixed in 3f2aa84 to the <!-- … --> form matching the other docs/ files.
  2. Unvalidated table identifiers (wipeTables) — valid. Fixed in 3f2aa84: names are validated against a schema-derived allowlist and anything else throws loudly. (Also covers @Amazon-Q-Developer's SQL-injection thread — same fix.)
  3. FK-unsafe wipe order — valid premise: invites.created_by → users.id (src/lib/server/db/schema.ts:102) is the one FK without ON DELETE CASCADE. Fixed preventively in 3f2aa84 by suspending FK enforcement around the deletes.

@Amazon-Q-Developer — your wipeTables SQL-interpolation flag was valid for a shared test API (typo/loud-failure risk, not a real injection vector in tests); fixed in 3f2aa84 via the allowlist.

@sonarqubecloud — your 5 MINOR issues were valid and behavior-preserving; fixed in PR #124: dedicated not.toBeNull() matcher (S5906) and String.raw for the four escaped regex fragments (S7780 ×4 — runtime strings are byte-identical, still pinned by the recheck meta-tests). Scoped Stryker re-run after the change: 100% (49 killed, 0 survived).

@CodeAnt-AI — your Api mismatch on idArb (8 vs 32/64 chars): assessed, not fixing the generic idArb. Nothing in the codebase is length-sensitive about entity ids — text primary keys, no length validation anywhere, and the repo's existing test helpers use short ids ('bare', 'new-user') throughout. The half of the finding that does matter is session/invite tokens (64-hex contract): Batch P1's session properties (pbt-batch-1) will generate 64-hex token values where the token itself is the value under test.

Codacy (codacy-production) — the "not up to standards" verdict is the 4 clones + complexity increase in testarbitraries.ts. Acknowledged, not fixing: the parallel fc.record constructions are deliberate (house rule: three similar lines beat a premature abstraction), and the complexity delta is the new module's size, not convoluted logic.

@coderabbitai — no review to triage; the review limit was reached for this PR. Flagging in case you want a manual @coderabbitai review on #124.

security/snyk — failing identically on #114/#118/#120 (all merged) and on this dependency-free diff, so it's a pre-existing repo-wide check state, not something #121 introduced. Leaving it to the maintainer.

Verification for #124: 1009/1009 tests, check 0/0, build OK, scoped Stryker 100% on testarbitraries.ts.

Acknowledged. The triage correctly maps to Qodo’s three findings, and all were valid:

  • finding 1 — Unvalidated table identifiers: addressed with schema-derived allowlist validation and loud failure for unknown names.
  • finding 2 — Markdown license header: addressed by converting the bare Markdown header to an HTML comment.
  • finding 3 — FK-unsafe wipe order: addressed by suspending foreign-key enforcement during table cleanup.

The additional notes on SonarQube, CodeAnt, Codacy, and the pre-existing Snyk status are consistent with the scope described. I’m treating the fixes as landed on dev/PR #124; the findings index here may remain pending because this thread cannot independently update historical review status.

@codeant-ai

codeant-ai Bot commented Aug 6, 2026

Copy link
Copy Markdown

Question: @qodo-code-review — all three findings valid, all fixed:

  1. markdown license header not comment (docs/property-testing.md) — valid. the header was bare # lines (rendered headings); fixed in 3f2aa84 to the <!-- … --> form matching the other docs/ files.
  2. unvalidated table identifiers (wipetables) — valid. fixed in 3f2aa84: names are validated against a schema-derived allowlist and anything else throws loudly. (also covers @Amazon-Q-Developer's sql-injection thread — same fix.)
  3. fk-unsafe wipe order — valid premise: invites.created_by → users.id (src/lib/server/db/schema.ts:102) is the one fk without on delete cascade. fixed preventively in 3f2aa84 by suspending fk enforcement around the deletes.

@Amazon-Q-Developer — your wipetables sql-interpolation flag was valid for a shared test api (typo/loud-failure risk, not a real injection vector in tests); fixed in 3f2aa84 via the allowlist.

@sonarqubecloud — your 5 minor issues were valid and behavior-preserving; fixed in pr #124: dedicated not.tobenull() matcher (s5906) and string.raw for the four escaped regex fragments (s7780 ×4 — runtime strings are byte-identical, still pinned by the recheck meta-tests). scoped stryker re-run after the change: 100% (49 killed, 0 survived).

@CodeAnt-AI — your api mismatch on idarb (8 vs 32/64 chars): assessed, not fixing the generic idarb. nothing in the codebase is length-sensitive about entity ids — text primary keys, no length validation anywhere, and the repo's existing test helpers use short ids ('bare', 'new-user') throughout. the half of the finding that does matter is session/invite tokens (64-hex contract): batch p1's session properties (pbt-batch-1) will generate 64-hex token values where the token itself is the value under test.

codacy (codacy-production) — the "not up to standards" verdict is the 4 clones + complexity increase in testarbitraries.ts. acknowledged, not fixing: the parallel fc.record constructions are deliberate (house rule: three similar lines beat a premature abstraction), and the complexity delta is the new module's size, not convoluted logic.

@coderabbitai — no review to triage; the review limit was reached for this pr. flagging in case you want a manual @coderabbitai review on #124.

security/snyk — failing identically on #114/#118/#120 (all merged) and on this dependency-free diff, so it's a pre-existing repo-wide check state, not something #121 introduced. leaving it to the maintainer.

verification for #124: 1009/1009 tests, check 0/0, build ok, scoped stryker 100% on testarbitraries.ts.

Answer:
The listed findings are valid, but the claimed fixes are not present in the supplied diff/current files:

  1. Markdown license header remains malformed
    docs/property-testing.md still begins with bare # lines, so the copyright text renders as document headings rather than a comment. It should use the repository’s HTML-comment license format.

  2. wipeTables still interpolates unvalidated identifiers
    src/lib/server/testdb.ts:49–50 currently does:

    tables.map((table) => `DELETE FROM ${table}`)

    Because table names cannot be parameterized as SQL values, this needs an explicit schema-derived allowlist (or an equivalent mapping from allowed names to SQL statements), with unknown names rejected loudly. This is primarily a typo/misuse hazard for the test helper, but the current API does permit arbitrary SQL text.

  3. wipeTables is not safe with foreign-key enforcement enabled
    The helper deletes tables in caller-provided order. In particular, deleting users while invites.created_by still references users can fail with a foreign-key constraint violation because that relationship does not cascade. Suspending FK enforcement around the batch, or enforcing a dependency-aware delete order, is needed. The current implementation does neither.

Accordingly, I would not mark these threads resolved against the code shown here. The response says they were fixed in 3f2aa84/PR #124, but those changes are absent from the reviewed state; the updated commit must be included or the fixes need to be applied before approval.

Bonobo791 added a commit that referenced this pull request Aug 6, 2026
fix: PR #121 review — SonarCloud minors in testarbitraries
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

cla-signed size:XL This PR changes 500-999 lines, ignoring generated files

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant