Skip to content

test(properties): cover the listing surface, fix bulk error handling and add listing log events - #1474

Merged
nanaf6203-bit merged 2 commits into
MettaChain:mainfrom
ustaxs:feature/listingapi-tests-errorhandling-logging
Oct 20, 2026
Merged

nanaf6203-bit merged 2 commits into
MettaChain:mainfrom
ustaxs:feature/listingapi-tests-errorhandling-logging

Conversation

@ustaxs

@ustaxs ustaxs commented Oct 18, 2026

Copy link
Copy Markdown
Contributor

Extends the Property API test pass (#1363–#1464, which covered create, lookups, remove and restore) over the listing routes — and fixes two real defects found while covering the bulk endpoints.

#1381 — Error handling (two genuine bugs)

Bulk status update silently published listings. POST /properties/bulk/status mapped the requested status with:

status === DRAFT ? 'DRAFT' : status === ARCHIVED ? 'ARCHIVED' : 'ACTIVE'

Any status that was not DRAFT or ARCHIVED fell through to ACTIVE. Bulk-marking listings SOLD or PENDING therefore published every one of them and returned a success-looking updatedCount. Only DRAFT/ACTIVE/ARCHIVED are accepted now; anything else is a 400 naming the supported set, with nothing written.

Bulk delete reported ids that were never deleted. POST /properties/bulk/delete returned { deletedCount, propertyIds } where propertyIds was the requested list echoed back unchanged. Deleting ['real-id', 'typo-id'] answered { deletedCount: 1, propertyIds: ['real-id', 'typo-id'] } — which reads as if both had been removed. It now resolves which ids actually exist first and returns those, so the count and the list always agree. Cost: one extra findMany({ select: { id: true } }) on an admin-only endpoint.

Foreign-key violations surfaced as opaque 500s. A listing held by a transaction, document or commission made Prisma throw P2003/P2014, reaching the client as an unhandled 500 carrying the raw driver message — internal schema detail in an error body. Now a 409 with the underlying error logged (properties.bulk_delete_conflict) rather than returned. Any other error still propagates unchanged.

Empty id lists are rejected with 400. The DTOs already enforced @ArrayMinSize(1) at the HTTP boundary, but the service methods are called directly too, and updateMany/deleteMany with an empty in list is a silent no-op reporting updatedCount: 0 — indistinguishable from "nothing matched".

→ docs/api/listings-error-handling.md

#1382 — Structured logging

Added the AppLogger events the listing paths never emitted:

Area Events
Search properties.search
Lifecycle properties.status_transitioned, properties.status_transition_noop, properties.approved, properties.rejected
Bulk properties.bulk_status_updated, properties.bulk_deleted, properties.bulk_delete_no_match, properties.bulk_delete_conflict, properties.bulk_exported
Expiry properties.expired
Agents properties.agent_assigned, properties.agent_assignment_updated, properties.agent_assignment_removed
Amenities properties.amenity_added, properties.amenity_updated, properties.amenity_removed

Two details worth reviewing:

  • properties.search logs how many filters were supplied, never their values — so "every search with 8 filters is slow" is diagnosable without addresses, cities or owner names reaching the log stream. Paging and sorting are excluded, since page/limit are always present because the DTO defaults them.
  • Every bulk event logs requestedCount alongside the affected count. That is the same gap the delete fix above closes, on the observability side.

→ docs/api/listings-structured-logging.md

#1379 — Unit tests

New suites over the listing surface that had none:

  • searchProperties — paging arithmetic and reported total, the defaults (page 1, 20/page, newest first), that the query is forced to ACTIVE and non-deleted whatever status the caller passed, the neighborhoodTrustScore annotation, the in-memory trust-score branch (and that it bypasses the transaction), and geo validation: lat without lng rejected, radius with no centre rejected, radius converted to a bounding box.
  • transitionStatus / approve / reject — 404 unknown, 403 for a caller who is neither owner/agent/admin, 400 for a disallowed transition, the idempotent no-op that does not write, the three roles that are allowed, and approve→ACTIVE vs reject→ARCHIVED.
  • Bulk operations — each supported status, rejection of an unsupported one, the empty-list guard, that propertyIds reports only ids that existed, the no-match short circuit, and P2003 → conflict.
  • Agent assignment — the 404/403/role/duplicate guards, the 3% default commission, 404 on updating or removing an assignment that does not exist, and the contact-detail fallback in getAgents.
  • Amenities — 404 unknown listing, refusal to touch an amenity belonging to a different listing, the isAvailable default, oldest-first ordering.

The Prisma mock gained the updateMany, deleteMany, count, $transaction, propertyAgent, propertyAmenity and user delegates.

→ docs/api/listings-tests.md

#1380 — Integration tests

The HTTP suite now drives the routes it did not touch:

  • Status lifecycle — open to any authenticated caller, forwarding the caller id and role; requires authentication; rejects a status outside the enum with 400; surfaces a refused transition as 400; approve/reject admin-only.
  • Bulk operations — admin-only, id-list validation (empty and malformed), the reported ids, a foreign-key conflict surfacing as 409, and the optional export filter.
  • Agent assignment — authentication, argument forwarding on create/update/remove, a missing assignment as 404, unauthenticated read.
  • Amenities — body validation, create, unauthenticated read, both-ids forwarding on update and delete.
  • Report download — a PDF is streamed with its attachment headers, a missing listing is 404, and an unexpected failure returns 500 without leaking the underlying error text into the body.

Not run

Per instructions nothing was installed, built, linted or tested. The new suites were written from the current service and controller code, and the expectations were read off that code rather than observed — please verify in CI, since assertions that never ran are the main risk here.

Closes #1379
Closes #1380
Closes #1381
Closes #1382

… add listing log events

Extends the Property API test pass (MettaChain#1363-MettaChain#1366, which covered create,
lookups, remove and restore) over the listing routes, and fixes two real
defects found while covering the bulk endpoints.

- MettaChain#1381: `POST /properties/bulk/status` mapped the requested status with
  `status === DRAFT ? DRAFT : status === ARCHIVED ? ARCHIVED : ACTIVE`, so any
  other status fell through to ACTIVE. Bulk-marking listings SOLD or PENDING
  silently published every one of them and returned a success-looking
  `updatedCount`. Only DRAFT/ACTIVE/ARCHIVED are accepted now and anything else
  is a 400 naming the supported set, with nothing written.
  `POST /properties/bulk/delete` echoed the *requested* ids back as
  `propertyIds`, so deleting ['real', 'typo'] answered deletedCount 1 with both
  ids listed, which reads as if both had been removed. It now resolves which ids
  exist first and returns those, so the count and the list always agree; when
  nothing matches it short-circuits without touching the database. A foreign-key
  violation (P2003/P2014, i.e. a listing held by a transaction, document or
  commission) was an unhandled 500 carrying the raw driver message; it is now a
  409 with the underlying error logged instead of returned. Both bulk methods
  reject an empty id list, which was previously a silent no-op indistinguishable
  from "nothing matched". docs/api/listings-error-handling.md
- MettaChain#1382: added the AppLogger events the listing paths never emitted - search
  (with the filter *count* but never the filter values, so a slow search can be
  attributed to a query shape without addresses reaching the log stream), status
  transition including the idempotent no-op, approve/reject, all three bulk
  operations (each logging requestedCount alongside the affected count), expiry,
  the three agent-assignment operations and the three amenity operations.
  docs/api/listings-structured-logging.md
- MettaChain#1379: unit suites added for searchProperties (paging, defaults, the forced
  ACTIVE/non-deleted filter, trust-score annotation and the in-memory branch,
  geo validation), transitionStatus/approve/reject (404/403/400, the no-op, the
  three allowed roles), bulk operations, agent assignment and amenities. The
  Prisma mock gained the updateMany/deleteMany/count/$transaction/propertyAgent/
  propertyAmenity/user delegates. docs/api/listings-tests.md
- MettaChain#1380: the HTTP suite now drives the status lifecycle (including that a
  refused transition is a 400 and that the caller role is forwarded), the bulk
  endpoints (id validation, reported ids, a 409 conflict), agent assignment,
  amenities, and the report download - which additionally asserts that an
  unexpected failure returns 500 without leaking the underlying error text.

No tests were run and nothing was installed, built or linted; the new suites
were written from the current service/controller code. Please verify in CI.

Closes MettaChain#1379
Closes MettaChain#1380
Closes MettaChain#1381
Closes MettaChain#1382
@drips-wave

drips-wave Bot commented Oct 18, 2026

Copy link
Copy Markdown

@ustaxs Great news! 🎉 Based on an automated assessment of this PR, the linked Wave issue(s) no longer count against your application limits.

You can now already apply to more issues while waiting for a review of this PR. Keep up the great work! 🚀

Learn more about application limits

@nanaf6203-bit

Copy link
Copy Markdown
Contributor

Tested it locally, works as expected. Merging.

@nanaf6203-bit nanaf6203-bit 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.

LGTM 👍

@nanaf6203-bit
nanaf6203-bit merged commit 5b8e59e into MettaChain:main Oct 20, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add structured logging to ListingAPI Improve error handling in ListingAPI Add integration tests for ListingAPI Add unit tests for ListingAPI

2 participants