Skip to content
PПромтбук
RUEN
04Тестирование

Аудит data + state integrity

Race conditions, optimistic-update revert, localStorage versioning, browser back/forward, multi-tab sync, retry-storms, stale cache. Самый сложный класс багов — невоспроизводимые «иногда работает».

«Иногда работает» — самые дорогие баги в product. Repro не повторяется, support не может объяснить, dev говорит «у меня нормально». В 90% случаев это state integrity: race condition, stale cache, версия localStorage не сматчилась, multi-tab расходится.

Этот промт — структурированный pass через 10 классов state-багов с конкретными test scenarios.

Stack: {{stack}} Mutations: {{primary_mutations}}

1. 10 классов state-багов

1. Race condition: double-submit

Scenario: user clicks Submit, network slow, clicks again before response. Bug: двойная запись в БД, два confirmation emails, payment charged twice. Test:

  1. Click Submit
  2. Immediately click Submit again (or 5x rapidly)
  3. Verify: только одна запись, button disabled во время request
  4. Use AbortController для cancel previous

2. Race condition: optimistic update revert

Scenario: UI shows «saved» immediately, server returns 500. Bug: UI lies — пользователь думает saved, реально нет. Test:

  1. DevTools → Block API endpoint → user clicks Save
  2. Verify: UI revert with error message
  3. State согласован: optimistic value не остаётся при server fail

3. Stale data after mutation

Scenario: user creates item, list не обновляется, refresh показывает реально создан. Bug: cache не invalidated после mutation. Test:

  1. Create new item
  2. Verify: appears в list без reload
  3. Edit item → verify: changes reflected везде где используется
  4. Delete item → исчезает из всех views

4. localStorage version mismatch

Scenario: user open app — v2 deployed, в localStorage v1 data. Bug: parsing fail, crash, потеря saved state. Test:

  1. Save value в localStorage в old format
  2. Deploy new version с different format
  3. Verify: graceful migration или clean state, не crash
  4. Pattern: {version: 2, data: {...}} + migration handler

5. Browser back/forward state

Scenario: user submit form → success page → back → form empty. Bug: form state lost; или хуже — re-submit happens на back. Test:

  1. Fill form, submit
  2. Navigate forward → success
  3. Browser back → form state как должен быть (depending on UX intent)
  4. Forward → success page без re-submission

6. Multi-tab synchronisation

Scenario: user open app в two tabs, edit в Tab A, Tab B показывает stale. Bug: untracked divergence, тяжёлый conflict resolution. Test:

  1. Open app in Tab A and Tab B same item
  2. Edit item в Tab A, save
  3. Verify: Tab B либо updates (через BroadcastChannel / storage event) либо показывает conflict warning
  4. Edit одновременно — last-write-wins acceptable если предсказуемо

7. Retry storm

Scenario: server slow, client retries automatically, retry creates more load, server slower. Bug: thundering herd, downtime amplification. Test:

  1. Throttle API to 5s timeout
  2. Verify: client backs off (exponential), не retries every 100ms
  3. Implement Retry-After header support
  4. Circuit breaker pattern: после N consecutive failures, stop retrying for X seconds

8. Concurrent mutations: write skew

Scenario: Two users edit same item simultaneously. Last write wins → first user's changes lost silently. Bug: lost updates, no conflict warning. Test:

  1. Get item в Tab A and Tab B
  2. Edit different fields в каждом tab
  3. Save A, then save B
  4. Verify: либо both changes preserved (merge), либо B gets conflict warning
  5. If-Match ETag header pattern или explicit version field

9. Timer / interval cleanup

Scenario: Component mounts, sets interval, unmounts but timer continues; second mount creates 2nd timer. Bug: memory leak, duplicate API calls, eventual crash. Test:

  1. Navigate to page with periodic refresh
  2. Navigate away
  3. Inspect: setInterval cleaned up в useEffect cleanup
  4. WebSocket / EventSource closed
  5. Subscriptions unsubscribed

10. Pagination / infinite scroll state

Scenario: user scrolls to page 5, opens detail, back → page 1 (lost position). Bug: scroll position lost, pagination state reset. Test:

  1. Scroll to deep page
  2. Click item → detail
  3. Back → должен restore scroll position И pagination cursor

2. Tools для debugging

Chrome DevTools

  • Application → Storage: inspect localStorage / IndexedDB / cookies
  • Network → Throttling: Slow 3G to surface race conditions
  • Performance → Recording: see when state changes, what triggered re-render
  • Sources → Conditional breakpoints: break когда условие на mutation
  • Memory → Heap snapshot: ловит uncleaned subscriptions / timers

React-specific

  • React DevTools → Profiler: видеть unnecessary re-renders
  • <StrictMode> в dev — двойной effect call → catches missing cleanup
  • React Query DevTools — visualise query state, cache, mutations
  • why-did-you-render — find re-renders без reason

State machines

Для complex state: XState / Zustand с history middleware. Visualise state transitions, catches impossible states.

3. Test methodologies

Stress test

  • 100 mutations / second on same resource
  • Multiple simulated users on same item
  • Network with random failures (10% drop rate via DevTools)

Property-based testing

  • fast-check: generate random inputs, verify invariants
  • «After any sequence of N mutations, total = expected»

Snapshot integrity tests

  • Save snapshot перед mutation
  • Apply mutation
  • Verify только targeted fields changed

4. Common patterns / solutions

AbortController

useEffect(() => {
  const ctrl = new AbortController();
  fetch(url, { signal: ctrl.signal })
    .then(...)
    .catch(e => e.name === 'AbortError' ? null : handleError(e));
  return () => ctrl.abort();
}, [url]);

Debounce + last-only

const debouncedSave = useMemo(() => debounce((val) => apiSave(val), 500), []);

Только последняя версия value уходит на сервер.

Optimistic update with rollback

const mutation = useMutation({
  onMutate: (newValue) => {
    const prev = queryClient.getQueryData(['item']);
    queryClient.setQueryData(['item'], newValue);
    return { prev };
  },
  onError: (err, _, ctx) => {
    queryClient.setQueryData(['item'], ctx.prev);
    showError(err);
  },
});

BroadcastChannel для multi-tab

const channel = new BroadcastChannel('app');
channel.postMessage({ type: 'item-updated', id });
channel.onmessage = (e) => { /* refetch */ };

LocalStorage versioning

const STORAGE_VERSION = 2;
function load() {
  try {
    const raw = localStorage.getItem('key');
    if (!raw) return defaultState();
    const parsed = JSON.parse(raw);
    if (parsed.version === STORAGE_VERSION) return parsed.data;
    if (parsed.version === 1) return migrateV1ToV2(parsed.data);
    return defaultState();  // unknown version
  } catch {
    return defaultState();
  }
}

5. Anti-patterns

  • ❌ Disable retry «у нас retries вызывают баги» — нужен правильный backoff
  • setState без проверки mounted в async handler — «Cannot update unmounted component»
  • ❌ Polling каждую секунду — burns battery, server load
  • ❌ Игнор failures — assume optimistic будет ОК всегда
  • ❌ Один global cache invalidation после ANY mutation — re-fetches всё, dropеёт perf
  • ❌ localStorage без try-catch — parse error → crash
  • ❌ localStorage без version → can't migrate gracefully
  • ❌ В multi-tab нет sync mechanism — divergence неизбежен
  • ❌ Test только happy path mutation — race conditions surface на slow network
  • ❌ «Это не повторяется» — повторяется, just не в твоём dev environment

6. Output

  1. Список 10 классов с pass/fail per primary mutation
  2. Top-5 race conditions found с repro steps + видео
  3. localStorage migration plan если нет versioning
  4. Multi-tab sync strategy decision (BroadcastChannel / storage event / server-side)
  5. Retry/backoff config для каждого critical API call
  6. Monitoring: что добавить в Sentry / DataDog для catching race conditions в prod
К подразделу «Тестирование»
Похожие промты