P0 / P1 · Chapter 05 / 15

Kafka / Async / Database

The 60-second cheat sheet#

An HTTP response can acknowledge work before an event-driven system finishes it. In the illustrative verification flow: request → producer → Kafka broker → consumer → database projection → status API → UI. A 202 response proves acceptance under the contract; a final business state and its correlated effects prove completion.

Delivery, ordering and consistency#

Concept What to test / avoid claiming
At-least-once delivery Duplicate processing is possible; consumers need business deduplication
Ordering Usually scoped to partition/key and producer/consumer configuration, not global
Retry Distinguish transient processing failure from permanently invalid input
Dead-letter queue Verify routing, diagnostic reason and recovery policy through approved hooks
Out-of-order events Version/sequence rule prevents older state overwriting newer state
Eventual consistency Observable delay is expected; bound convergence by agreed service objective
Transaction boundary DB commit, event publication and projection visibility may be separate

Kafka exactly-once features have a defined transactional boundary and do not automatically make arbitrary database/external side effects exactly once. Test idempotent handlers with the same business/event ID and verify one logical effect, including after consumer restart.

Bounded polling for completion#

typescript
// tests/api/verification.spec.ts: complete module, illustrative sandbox contract.
import { test, expect } from '@playwright/test';
test('accepted verification reaches final state', async ({ request }) => {
  // Fixture-provided unique user assumed; no production accounts.
  const id = process.env.QA_VERIFICATION_USER_ID;
  if (!id) throw new Error('Unique sandbox user required');
  const path = `/api/users/${encodeURIComponent(id)}/verify`;
  const accepted = await request.post(path);
  expect(accepted.status()).toBe(202);
  let observed = 'UNOBSERVED';
  await expect.poll(async () => {
    const response = await request.get(`/api/users/${encodeURIComponent(id)}/verification`);
    expect(response.status()).toBe(200);
    const body: unknown = await response.json();
    if (!body || typeof body !== 'object' || !('status' in body) || typeof body.status !== 'string') {
      throw new Error('Invalid status response');
    }
    observed = body.status;
    return ['VERIFIED', 'REJECTED'].includes(observed);
  }, { timeout: 20_000, intervals: [200, 500, 1_000], message: 'verification terminal state' }).toBe(true);
  expect(observed).toBe('VERIFIED'); // REJECTED is terminal but fails immediately here
});

This poll stops on either known terminal outcome, then asserts success. Exceptions inside poll may be retried; a malformed/unavailable response is reported by the final poll failure and should include diagnostics. If unknown states must abort immediately, use a dedicated bounded polling helper with explicit error classification. Set test budget above the poll plus setup/teardown. Do not repeat the initiating POST in the polling callback.

End-to-end evidence plan#

Advanced: stop immediately on auth/schema/terminal failures

expect.poll is convenient, but retrying every exception can conceal a permanent 401 or schema defect until the deadline. The helper below retries only documented transient reads/PENDING, fails immediately on rejected/invalid states and bounds elapsed time. It depends on an injected Playwright APIRequestContext and a sandbox endpoint. A response timeout after starting verification still requires reconciliation before replaying the mutation.

typescript
import type { APIRequestContext } from '@playwright/test';
import { performance } from 'node:perf_hooks';
export async function waitVerified(api: APIRequestContext, path: string, timeoutMs = 15_000): Promise<void> {
  const deadline = performance.now() + timeoutMs;
  while (performance.now() < deadline) {
    const remaining = Math.max(1, Math.floor(deadline - performance.now()));
    const response = await api.get(path, { timeout: remaining });
    const code = response.status();
    if (code !== 200 && code !== 503) throw new Error(`Fatal status read: HTTP ${code}`);
    if (code === 200) {
      const body: unknown = await response.json();
      if (!body || typeof body !== 'object' || !('status' in body)) throw new Error('Invalid status schema');
      if (body.status === 'VERIFIED') return;
      if (body.status !== 'PENDING') throw new Error(`Terminal/unknown state: ${String(body.status)}`);
    }
    const delay = Math.min(500, Math.max(0, deadline - performance.now()));
    await new Promise<void>(resolve => setTimeout(resolve, delay));
  }
  throw new Error('Verification completion deadline exceeded');
}

The short delay is a polling interval between meaningful reads, not a fixed assertion substitute. Here 503 retry is an explicit hypothetical contract policy; transport exceptions fail immediately. Extend only with classified transient transport handling and cancellation if the real environment requires it. Request timeouts bound calls, not every conceivable body-parsing or process-scheduling delay.

  1. Provision an authorized unique sandbox user and correlation ID; stub external identity/payment side effects.
  2. Submit verification and assert 202 plus accepted operation ID if documented.
  3. Through public status API poll to the contract's terminal state; verify the UI reflects that same user's result.
  4. With authorized hooks, inspect Kafka key/event ID/schema and consumer/projection logs; check DB row and audit record for the same correlation.
  5. Test duplicate delivery, delayed consumption, rejection, consumer recovery and dead-letter handling separately.
  6. Clean records or use documented TTL; retain sanitized failure evidence.

Public API completion proves externally observable behavior, not exactly which internal consumer ran. Kafka UI or DB access requires approved test harness permissions; do not pretend a browser test automatically observes internal events. A UI heading alone proves neither accepted event nor persisted result.

Reconciliation and ETL validation#

Check Concrete method
Completeness Expected source IDs minus target IDs; count alone can hide substitutions
Duplicates Group by business/event ID and flag count > 1
Accuracy Compare amount/currency/status with explicit mapping and decimal rules
Integrity Foreign keys, orphan detection, mandatory fields, state consistency
Freshness Compare UTC event/ingestion timestamps against agreed window
Totals Reconcile by currency/time window; never sum unrelated currencies
Lineage Source key + correlation + transformation version + target identity

After-write reads can race with transaction commit or read replicas. Retry only where eventual consistency is expected; retrying an invariant on a strongly consistent endpoint may hide a bug. For multi-query reconciliation, choose a stable snapshot/watermark or allow a documented lag window. Align time zones and late-arriving events before classifying missing data. FINA ETL/data integrity experience provides a grounded story; add only true ownership/results.

Observability and six scenarios#

Scenario Short ideal answer Detailed diagnostic route
202 but UI stays PENDING Acceptance is not completion Follow operation ID through producer, broker, consumer lag, DB projection and status API; poll within objective
Duplicate verified event Assert one logical effect Check dedupe key persistence across restarts, audit/ledger duplicates and processing transaction
Old event resets status Define ordering/version contract Use same key, controlled out-of-order versions, verify older version ignored or handled explicitly
DB correct, API stale Check read path/replica/cache Compare commit time, replica lag, cache invalidation, correlation and declared consistency
Record counts match but totals differ Counts insufficient Reconcile keys, decimal/currency mapping, duplicates and missing paired rows
Consumer fails repeatedly Bound retries and DLQ policy Inject safe controlled failure, inspect retry count/reason, DLQ ownership and authorized replay

Interview response#

30 seconds: “I distinguish transport acceptance from final business state. I correlate the request, event, projection and API result, poll a meaningful terminal state with a bounded deadline, and test duplicates and ordering according to the actual Kafka contract. For reconciliation I compare keys and values as well as counts.”

90-second senior answer: async verification investigation

I first confirm whether the endpoint promises synchronous completion or only acceptance. For 202 I record operation/correlation identity, then poll the supported status endpoint rather than sleep. On timeout I inspect where evidence stops: event publication, broker partition, consumer processing, database commit, read model or UI refresh. I distinguish consumer lag from a permanent rejection and verify error/DLQ handling. Duplicate events should not create duplicate business effects; out-of-order behavior depends on keys and version rules. A DB check needs the correct consistency boundary and a safe read account. I report which layer is proven by each observation, protect external financial side effects, and retain enough evidence to make the failure reproducible.

Memory hook: accepted is a beginning; correlate, converge, reconcile.

Quick practice · reveal answers#

What does HTTP 202 prove?
  • Acceptance according to contract, not final outcome.
  • Poll the operation/status endpoint to a terminal business state.

Pitfall: Equating HTTP code with VERIFIED status field.

Full explanation →

How do you test Kafka duplicates?
  • Send controlled duplicate business/event identity and verify one logical DB/audit/ledger effect.
  • Include restart boundary when relevant.

Pitfall: Assuming delivery semantics guarantee all external effects once.

Full explanation →

Is Kafka globally ordered?
  • Ordering depends on partition/key and configuration.
  • Specify the exact ordering/version contract and test out-of-order handling.

Pitfall: Expecting global order across unrelated partitions.

Full explanation →

How do you reconcile source and target?
  • Compare keys both directions
  • duplicates, values and compatible grouped totals with a stable watermark/snapshot.

Pitfall: Equal counts imply identical records.

Full explanation →

Official references and related topics#

Optional online further reading; all core study material is included above.

Back to library index

Prepared for both Senior automation roles. Hypothetical sandbox contracts; no employer systems or credentials. Content: 16 October 2026.