Skip to content

fix(contact): safe spam handling and actionable validation - #168

Merged
WilliamAGH merged 5 commits into
mainfrom
dev
Aug 5, 2026
Merged

fix(contact): safe spam handling and actionable validation#168
WilliamAGH merged 5 commits into
mainfrom
dev

Conversation

@WilliamAGH

@WilliamAGH WilliamAGH commented Aug 5, 2026

Copy link
Copy Markdown
Owner

Summary

Contact submissions now drop non-positive render timestamps before rate-limit or SMTP work and show actionable client-side validation guidance. Exact Java 21 and Java 24 List.of(E,E) citation coverage confirms the reported citation failure is not reproducible when both releases provide authoritative exact-member evidence.

Changes by Category

Bug Fixes

  • Safe Contact Spam Handling: Treat omitted, zero, and negative renderedAt values as spam while preserving the indistinguishable 202 accepted response.
  • Actionable Contact Validation: Show specific guidance for blank names and messages, malformed email addresses, and over-length fields.

Refactoring

  • Validation Message Ownership: Keep production guidance in domain-qualified schema constants while tests retain independent rendered-copy expectations.

Testing

  • Multi-Release Citation Contract: Verify Java 21 and Java 24 exact Oracle citations, dedicated multi-release dispatch, and no generic retrieval or reranking.
  • Contact Admission Boundary: Verify 0, -1, and Long.MIN_VALUE timestamps do not send mail or consume the same-IP allowance before a legitimate request.
  • Client Validation Coverage: Verify invalid forms render exact messages and never call the contact API.

Test plan

  • Local full pre-push gate: 1,042 JVM tests, frontend validation, production build, formatting, and static analysis passed.
  • GitHub Actions push run 31043583245: frontend, build, and Docker smoke passed.
  • GitHub Actions pull-request run 31043582929: frontend, build, and Docker smoke passed.
  • Coolify deployment e9g7gyluymfkocvt97vuo5bw finished at exact SHA b368011c2785bd5d85bada4f9552c90ff6393625; readiness and liveness are UP.
  • Fresh mobile contact dogfood verified exact validation copy, field ARIA state, no overflow, and no /api/contact request.
  • Exact Java 21/24 List.of(E,E) chat dogfood returned both Oracle sources with matching anchors.
  • Bounded Loki checks found no warning/error/fatal, runtime retrieval failure, or unexpected contact events; active Java Chat alerts were empty.

Breaking changes

None.

Related issues

Cover the static citation path with exact List.of overload evidence from
Java 21 and 24, preserving strict anchor fidelity.
Treat non-positive client render times as spam before reserving rate-limit
capacity or attempting SMTP delivery, while preserving the 202 response.
Give each visible contact field user-facing Zod messages so required and
malformed input no longer collapses to generic dependency copy.
Test exact inline guidance and preserve client-side rejection.
Copilot AI lite review requested due to automatic review settings August 5, 2026 19:59
@coderabbitai

coderabbitai Bot commented Aug 5, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Summary by CodeRabbit

  • Bug Fixes

    • Contact form submissions now show clear validation messages for missing or invalid names, email addresses, and messages.
    • Improved protection against automated contact submissions with invalid or missing timing information.
    • Fixed citation discovery across multiple Java releases, including accurate release-specific links and anchors.
  • Tests

    • Added coverage for contact form validation, spam prevention, and multi-version citation results.

Walkthrough

The changes add explicit contact-form validation messages, reject non-positive render timestamps as spam, and add regression coverage for multi-version citation discovery and contact submission handling.

Changes

Contact validation feedback

Layer / File(s) Summary
Contact validation messages
frontend/src/lib/validation/schemas.ts, frontend/src/lib/components/ContactPage.test.ts
The contact schema now defines field-specific validation messages. The component test verifies messages for invalid name, email, and message values.

Timestamp spam detection

Layer / File(s) Summary
Non-positive timestamp guard
src/main/java/com/williamcallahan/javachat/application/contact/ContactSubmissionUseCase.java, src/test/java/com/williamcallahan/javachat/web/ContactControllerTest.java
The contact use case treats zero and negative render timestamps as spam. Parameterized tests verify accepted responses without email delivery.

Citation discovery regression

Layer / File(s) Summary
Multi-version citation coverage
src/test/java/com/williamcallahan/javachat/service/RetrievalServiceTest.java
The regression test verifies exact citations, release-specific URLs, and of(E,E) anchors for Java 21 and Java 24.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Possibly related PRs

Suggested labels: bug, java, javascript

Suggested reviewers: copilot

Poem

Clear errors guide the form,
Timestamps guard the mail,
Java citations cross releases,
Tests keep each path on trail,
Small checks make strong sails.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Title check ✅ Passed The title clearly summarizes the primary contact spam-handling and validation changes.
Description check ✅ Passed The description directly explains the contact, validation, citation, and testing changes in the pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch dev

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.

@coderabbitai coderabbitai Bot added bug Something isn't working java Pull requests that update java code javascript Pull requests that update javascript code labels Aug 5, 2026

@coderabbitai coderabbitai 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.

Actionable comments posted: 1

🧹 Nitpick comments (4)
frontend/src/lib/components/ContactPage.test.ts (1)

91-93: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Use the canonical validation-message owner.

Lines [91]-[93] duplicate messages owned by frontend/src/lib/validation/schemas.ts. Export the canonical message constants and import them here. Keep the getByText assertions so the test still verifies the rendered guidance without creating a second source of truth.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@frontend/src/lib/components/ContactPage.test.ts` around lines 91 - 93, Export
the canonical validation-message constants from the schemas module and import
them into the ContactPage test. Update the three getByText assertions to use
those constants instead of duplicating literal messages, preserving the existing
rendered-guidance checks.

Source: Coding guidelines

src/test/java/com/williamcallahan/javachat/web/ContactControllerTest.java (1)

138-155: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Cover the rate-limit boundary in this regression test.

This test proves that JavaMailSender.send is not called. It does not prove that a non-positive timestamp leaves acceptedSubmissionsPerIp unchanged. If the spam check moved after the counter increment, these three invocations could still return 202 and the test would pass.

Add a same-IP sequence with four non-positive submissions followed by one legitimate submission. Assert 202 and one mail send for the legitimate submission. This covers the stated PR requirement that non-positive timestamps are handled before rate limiting.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/test/java/com/williamcallahan/javachat/web/ContactControllerTest.java`
around lines 138 - 155, Extend
nonPositiveRenderTimestampIsDroppedSilentlyWithoutSendingMail to submit four
non-positive timestamp requests from the same IP, then submit one legitimate
request from that IP. Assert each request remains accepted, verify the
legitimate request triggers exactly one JavaMailSender.send call, and retain the
existing no-mail assertion for the invalid submissions to confirm they do not
consume the rate-limit counter.
src/test/java/com/williamcallahan/javachat/service/RetrievalServiceTest.java (2)

468-491: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Verify the multi-release dispatch explicitly.

The exact when(...) stub does not assert call count or reject an extra retrieval or reranking call. Add verify(...) for searchDocumentationCitationsOutcomes(...) with both release constraints. Also verify that searchOutcome(...) and rerank(...) are never called. This protects the route contract, not only the returned citations.

Suggested assertion
         assertEquals(0, citationOutcome.failedConversionCount());
+        verify(hybridSearchService)
+                .searchDocumentationCitationsOutcomes(
+                        eq(exactComparisonQuery),
+                        eq(10),
+                        eq(List.of(java21Constraint, java24Constraint)),
+                        anyLong());
+        verify(hybridSearchService, never())
+                .searchOutcome(anyString(), anyInt(), any(RetrievalConstraint.class), anyLong());
+        verify(rerankerService, never()).rerank(anyString(), anyList(), anyInt(), anyLong());
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/test/java/com/williamcallahan/javachat/service/RetrievalServiceTest.java`
around lines 468 - 491, Add explicit Mockito verification after
discoverCitations in the multi-release test: verify
searchDocumentationCitationsOutcomes is called exactly once with
exactComparisonQuery, 10, both release constraints, and anyLong; verify
searchOutcome(...) and rerank(...) are never called. Use the existing service
and constraint symbols in the test.

463-467: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Extract the new magic literals into named constants.

The test introduces inline release identifiers, 10, query text, fixture IDs, hashes, the of(E,E) anchor, and expected URLs. Define domain-qualified constants and use them in the constraints, fixtures, stub, and assertions. Keep expected URL constants independent from DocsSourceRegistry so the test remains an exact-output oracle.

As per coding guidelines, No inline numbers (except 0, 1, -1) or strings; define named constants (Magic Literals).

Also applies to: 477-487

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/test/java/com/williamcallahan/javachat/service/RetrievalServiceTest.java`
around lines 463 - 467, Extract the inline release identifiers, query text,
fixture IDs, hashes, the of(E,E) anchor, and expected URLs used by the
RetrievalServiceTest scenario into clearly named, domain-qualified constants.
Reuse those constants in officialDocumentationConstraint, the exact overload
fixtures, stubs, and assertions, including the additional values in the
referenced lines. Keep expected URL constants defined independently from
DocsSourceRegistry so assertions remain exact-output oracles.

Source: Coding guidelines

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@frontend/src/lib/validation/schemas.ts`:
- Around line 137-147: Define domain-qualified named constants for the contact
name, email, and message validation errors in ContactSubmissionSchema’s module,
then use those constants instead of inline literals. In
frontend/src/lib/components/ContactPage.test.ts lines 91-93, import and use the
canonical constants rather than repeating the messages; update both affected
sites accordingly.

---

Nitpick comments:
In `@frontend/src/lib/components/ContactPage.test.ts`:
- Around line 91-93: Export the canonical validation-message constants from the
schemas module and import them into the ContactPage test. Update the three
getByText assertions to use those constants instead of duplicating literal
messages, preserving the existing rendered-guidance checks.

In
`@src/test/java/com/williamcallahan/javachat/service/RetrievalServiceTest.java`:
- Around line 468-491: Add explicit Mockito verification after discoverCitations
in the multi-release test: verify searchDocumentationCitationsOutcomes is called
exactly once with exactComparisonQuery, 10, both release constraints, and
anyLong; verify searchOutcome(...) and rerank(...) are never called. Use the
existing service and constraint symbols in the test.
- Around line 463-467: Extract the inline release identifiers, query text,
fixture IDs, hashes, the of(E,E) anchor, and expected URLs used by the
RetrievalServiceTest scenario into clearly named, domain-qualified constants.
Reuse those constants in officialDocumentationConstraint, the exact overload
fixtures, stubs, and assertions, including the additional values in the
referenced lines. Keep expected URL constants defined independently from
DocsSourceRegistry so assertions remain exact-output oracles.

In `@src/test/java/com/williamcallahan/javachat/web/ContactControllerTest.java`:
- Around line 138-155: Extend
nonPositiveRenderTimestampIsDroppedSilentlyWithoutSendingMail to submit four
non-positive timestamp requests from the same IP, then submit one legitimate
request from that IP. Assert each request remains accepted, verify the
legitimate request triggers exactly one JavaMailSender.send call, and retain the
existing no-mail assertion for the invalid submissions to confirm they do not
consume the rate-limit counter.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 65aa880c-1ac0-4d9e-8e83-6062ca53e06e

📥 Commits

Reviewing files that changed from the base of the PR and between 4f0e249 and 8e94373.

📒 Files selected for processing (5)
  • frontend/src/lib/components/ContactPage.test.ts
  • frontend/src/lib/validation/schemas.ts
  • src/main/java/com/williamcallahan/javachat/application/contact/ContactSubmissionUseCase.java
  • src/test/java/com/williamcallahan/javachat/service/RetrievalServiceTest.java
  • src/test/java/com/williamcallahan/javachat/web/ContactControllerTest.java

Comment thread frontend/src/lib/validation/schemas.ts Outdated

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

This PR strengthens contact-form spam defenses by treating missing/zero/negative renderedAt timestamps as spam (while preserving the existing HTTP 202 response), improves client-side validation messaging for contact submissions, and adds regression tests around contact admission plus multi-version Java API citation discovery.

Changes:

  • Backend: treat renderedAt == null or renderedAt <= 0 as spam and drop the submission before rate limiting / SMTP.
  • Frontend: provide actionable Zod validation messages for name/email/message fields; add UI test expectations for those messages.
  • Tests: add coverage ensuring non-positive renderedAt submissions never send mail; add a discoverCitations multi-version citation test.

Reviewed changes

Copilot reviewed 5 out of 5 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
src/test/java/com/williamcallahan/javachat/web/ContactControllerTest.java Adds parameterized coverage ensuring non-positive renderedAt values are silently accepted (202) without sending mail.
src/test/java/com/williamcallahan/javachat/service/RetrievalServiceTest.java Adds a discoverCitations multi-version Java 21/24 List.of(E,E) citation regression test (but does not reproduce Issue #165’s failure mode).
src/main/java/com/williamcallahan/javachat/application/contact/ContactSubmissionUseCase.java Updates spam detection to treat missing or non-positive renderedAt as spam.
frontend/src/lib/validation/schemas.ts Improves contact schema validation messages to be user-actionable.
frontend/src/lib/components/ContactPage.test.ts Asserts the new validation copy is rendered and that invalid submissions skip the API call.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Keep field-specific error copy with the contact schema while preserving
independent rendered-copy expectations at the UI test boundary.
Prove spam timestamps leave same-IP quota untouched and lock exact
multi-release citation discovery to its dedicated dispatch path.
@WilliamAGH
WilliamAGH merged commit 8d3ee70 into main Aug 5, 2026
8 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working java Pull requests that update java code javascript Pull requests that update javascript code

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants