Skip to content
bitzorcas
中EN

Guide

Comments testing, operations, and GA gates

Inventory current Comments tests and define missing contracts, observability, capacity, moderation, migration reconciliation, incident response, and release-blocking gates.

Last updated

Comments is small in lines of code, not in blast radius. A free-form subject key can cross business boundaries, bodies contain user-generated content, soft deletion must preserve a tree, and migration clears legacy data. GA acceptance must center on resource access, tenant consistency, and recoverability.

1. Current test assets

The current repository contains:

  • one Application query test covering store delegation, reply projection, and a deleted placeholder;
  • three Comments Infrastructure architecture tests for ORM neutrality, store usage by the query handler, and fail-closed persistence composition;
  • one dual-ORM PortRepositoryParity scenario covering Insert, Update, SoftDelete, and IncludeDeleted;
  • API-shell build smoke resolution for ICommentStore;
  • persistence-metadata registration coverage for CommentThreadSupportRecord.

There are no dedicated tests for Create, Update, Delete, migration, generic permissions, HTTP contracts, concurrent idempotency, owner authorization, deep trees, or tenant impersonation.

2. Test layers

E2E
Auth + owner + Comments + database

Contract/integration
HTTP / permission / tenant / ORM / migration

Unit
Rules / tree / time window / input

Handler unit tests

  • Create required inputs/10k, existing result, missing/deleted parent, and AuthorId;
  • Update author/admin/window boundary/trim/EditedAt;
  • Delete author/admin/repeated deletion;
  • GetComments ordering, orphan, deleted placeholder, and cycle protection;
  • Migration counts, corrupt JSON, replay, failure, and cancellation.

Persistence and contract tests

  • unique index, soft delete, include-deleted, and tenant filter in SqlSugar and EF Core;
  • one row and one consistent result from concurrent first requests;
  • all four use cases where User.TenantId differs from EffectiveTenantId;
  • every route, HTTP method, Problem Details shape, timeout, and rate-limit choice;
  • Documents/Tickets owner policy for view/create/update/delete;
  • migration source/target reconciliation and actual transaction scope.

3. Subject-authorization red test

Prevent a generic permission from bypassing the owner
[Fact]
public async Task Create_Should_Reject_When_Subject_Is_Not_Visible()
{
// The caller may create comments generally but cannot see this private Ticket.
using var client = fixture.CreateClient(
tenantId: "tenant-a",
userId: "outsider",
permissions: ["comments.comment.create"]);
await fixture.SeedPrivateTicketAsync(
"tenant-a", "ticket-42", ownerId: "owner");
// Free-form EntityType/EntityId must not bypass the Ticket-owned policy.
using var response = await client.PostAsJsonAsync(
"/api/comments/",
new
{
commentId = Guid.NewGuid(),
entityType = "Ticket",
entityId = "ticket-42",
body = "probe"
});
response.StatusCode.ShouldBe(HttpStatusCode.Forbidden);
(await fixture.CountCommentsAsync(
"tenant-a", "Ticket", "ticket-42")).ShouldBe(0);
}

The current implementation creates the row after generic permission succeeds, so this is intentionally a red test until subject policy exists.

4. Tenant-consistency contract

Idempotency while impersonating a tenant
await using var scope = fixture.CreateScope(
userTenantId: "operator-home",
effectiveTenantId: "tenant-target");
var command = new CreateComment.Command(
operationId, "Document", "doc-42", null, "tenant-scoped comment");
// Lookup and insert must both use tenant-target rather than mixing tenant sources.
var first = await scope.Mediator.Send(command);
var replay = await scope.Mediator.Send(command);
// Idempotent replay must return the same physical comment.
first.Value!.Id.ShouldBe(replay.Value!.Id);
// Prove that the operator's home tenant received no accidental write.
(await scope.Comments.CountAsync("tenant-target")).ShouldBe(1);
(await scope.Comments.CountAsync("operator-home")).ShouldBe(0);

Apply the same tenant matrix to Get, Update, Delete, and Ticket migration.

5. Observability without leaking content

Do not put body, author id, or raw EntityId into normal logs or high-cardinality metrics. Useful dimensions include:

DimensionExample values
use_casecreate/get/update/delete/migrate
resultsuccess/validation/forbidden/not_found/conflict/dependency
entity_typelow-cardinality canonical registry code
tenant_hashirreversible short hash
actor_typeuser/client/moderator
operation_kindroot/reply/edit/author_delete/moderator_delete

Track latency/result, owner-policy denials, idempotency replay/conflict, thread size/depth, body-length buckets, soft-delete rate, migration remainder/failure/reconciliation delta, database unique conflicts, and sanitizer rejections.

6. Alerts and incident response

SignalLikely causeFirst response
sudden comment growth on one subjectspam or absent owner checkrate-limit/disable Create; inspect actor and subject
unique conflicts riseconcurrent retries or tenant-source splitinspect CommentId and User/EffectiveTenant
GET P95/response size riseshot unpaged threadthrottle subject; enable paging/depth limits
StackOverflow/500cycle or extreme reply chainisolate subject; export and repair ParentCommentId
non-member read a threadabsent/stale owner policyblock GET, audit access, invalidate cache
source cleared but target missingtenant mismatch/transaction errorstop migration, restore backup, reconcile hashes
XSS reportraw UI renderingdisable rich rendering; sanitize and inspect history

7. Capacity and pagination

One current GET loads and builds the entire thread. Capacity tests should cover:

  • 1, 10, 100, 1,000, and 10,000 comments;
  • a 10,000-deep chain and one root with 10,000 children;
  • 50% soft-deleted placeholders;
  • 10k-character bodies with response compression;
  • concurrent read/write on one hot subject;
  • multi-tenant execution plans for subject, parent, and author indexes.

A target API can cursor-page root comments, include only the first N replies plus replyCount, and page children by ParentCommentId. Use CreateTime + CommentId for deterministic order.

8. Reconciliation checks

Run read-only checks for:

  • EntityType absent from the registry;
  • owner resource missing for EntityId;
  • ParentCommentId crossing a subject/tenant or forming a cycle;
  • IsDeleted=true with a non-null Body;
  • AuthorId=0;
  • body/id values beyond the contract or suspicious Unicode;
  • duplicate CommentId within a subject;
  • non-[] Ticket.CommentsJson after migration;
  • CommentId/Body-hash mismatches between the Ticket source and SysComment.

9. GA release blockers

  • EntityType uses a registry; unknown values fail closed and casing is canonical.
  • Every Get/Create/Update/Delete calls the subject owner’s policy.
  • Documents.AllowComments and Ticket visibility/state rules actually execute.
  • User.TenantId and EffectiveTenantId are unified with impersonation/background contract tests.
  • Create has an explicit ActorType/ActorId and no longer writes AuthorId=0.
  • The admin string becomes a moderator capability/policy with audit evidence.
  • Concurrent idempotency normalizes unique races and defines soft-delete replay.
  • Application validates identifier and body limits before persistence.
  • GET has pagination, depth/total limits, cycle protection, and stable ordering.
  • Rich-text/Markdown output uses a safe renderer and sanitizer.
  • Events, notifications, and moderation are implemented or explicitly excluded from product promises.
  • Ticket migration tenant, transaction, paging, corrupt-data, reporting, and reconciliation defects are fixed.
  • The long-lived Tickets.Infrastructure dependency is removed.
  • Handler, HTTP, permission, owner, dual-ORM, concurrency, capacity, and restore tests pass.

10. Verification commands

Terminal window
# Current Comments-focused tests.
dotnet test tests/BitzOrcas.Application.Tests/BitzOrcas.Application.Tests.csproj \
--filter 'FullyQualifiedName~Comments'
dotnet test tests/BitzOrcas.Architecture.Tests/BitzOrcas.Architecture.Tests.csproj \
--filter 'FullyQualifiedName~CommentsInfrastructureArchitectureTests'
dotnet test tests/BitzOrcas.Integration.Tests/BitzOrcas.Integration.Tests.csproj \
--filter 'FullyQualifiedName~CommentStore_Should_Behave'
# Sweep for a subject policy and for the current tenant/author split.
rg -n 'EnsureCanComment|ICommentSubjectPolicy|AllowComments' src/Platform -g '*.cs'
rg -n 'User.TenantId|EffectiveTenantId|AdminRole|\?\? "0"' \
src/Platform/Comments -g '*.cs'

Back to Comments

100%

Scroll or use controls to zoom · drag when enlarged · double-click for 100% / 200%