waybetter-for-x/apps/desktop/tests/mutationGate.test.ts
2026-10-01 06:01:13 +07:00

620 lines
32 KiB
TypeScript

import assert from 'node:assert/strict';
import { test } from 'node:test';
import { createLanes, type LaneRequest, type Settled } from '../src/fixes/mutationGate/lanes.ts';
import { classifyResponse, matchMutation, synthesizedBody, type MutationMatch } from '../src/fixes/mutationGate/matchers.ts';
import { installMutationGate } from '../src/fixes/mutationGate/index.ts';
import { X_FEATURES } from '../src/areas/table.ts';
import { FEATURE_DEFAULTS } from '../src/inject/features.ts';
const BASE = 'https://x.com/home';
const post = (body: BodyInit | null | undefined): RequestInit => ({ method: 'POST', body });
const likeBody = (id: unknown) => JSON.stringify({ variables: { tweet_id: id }, queryId: 'lI07N6Otwv1PhnEgXILM7A' });
const tick = () => new Promise(resolve => setImmediate(resolve));
// ---------- matchers ----------
test('matcher: like/unlike and follow/unfollow fixtures', () => {
assert.deepEqual(matchMutation('/i/api/graphql/lI07N6Otwv1PhnEgXILM7A/FavoriteTweet', post(likeBody('1790000000000000001')), BASE),
{ kind: 'like', id: '1790000000000000001', key: 'tweet:1790000000000000001', want: true });
assert.deepEqual(matchMutation(new URL('https://x.com/i/api/graphql/ZYKSe-w7KEslx3JhSIk5LA/UnfavoriteTweet'), post(likeBody('42')), BASE),
{ kind: 'like', id: '42', key: 'tweet:42', want: false });
assert.deepEqual(matchMutation('https://x.com/i/api/1.1/friendships/create.json', post('include_profile_interstitial_type=1&user_id=783214&skip_status=1'), BASE),
{ kind: 'follow', id: '783214', key: 'user:783214', want: true });
assert.deepEqual(matchMutation('/i/api/1.1/friendships/destroy.json', post(new URLSearchParams({ user_id: '783214' })), BASE),
{ kind: 'follow', id: '783214', key: 'user:783214', want: false });
// Promoted like bodies carry extra variables and still match.
assert.equal(matchMutation('/i/api/graphql/x/FavoriteTweet', post(JSON.stringify({ variables: { tweet_id: '7', promotedContent: { impressionId: 'a' } } })), BASE)?.key, 'tweet:7');
});
test('matcher: malformed, DM, read and other endpoints are untouched', () => {
const none: [unknown, RequestInit | undefined][] = [
['/i/api/graphql/x/FavoriteTweet', post('{not json')],
['/i/api/graphql/x/FavoriteTweet', post(likeBody(42))],
['/i/api/graphql/x/FavoriteTweet', post(likeBody('12a'))],
['/i/api/graphql/x/FavoriteTweet', post(JSON.stringify({ variables: {} }))],
['/i/api/graphql/x/FavoriteTweet', post('null')],
['/i/api/graphql/x/FavoriteTweet', post(new Blob(['x']))],
['/i/api/graphql/x/FavoriteTweet', { body: likeBody('1') }],
['/i/api/graphql/x/FavoriteTweet', undefined],
['/i/api/graphql/x/FavoriteTweets', post(likeBody('1'))],
['/i/api/graphql/x/CreateRetweets', post(likeBody('1'))],
['/i/api/graphql/x/DeleteRetweet', post(likeBody('1'))],
['/i/api/graphql/x/CreateBookmark', post(likeBody(1))],
['/i/api/graphql/x/DeleteBookmark', post(new Blob(['x']))],
['/i/api/graphql/x/TweetDetail', post(likeBody('1'))],
['/i/api/graphql/x/useDMReactionMutationAddMutation', post(likeBody('1'))],
['/i/api/1.1/dm/new2.json', post('user_id=1')],
['/i/api/1.1/friendships/cancel.json', post('user_id=1')],
['/i/api/1.1/friendships/update.json', post('user_id=1')],
['/i/api/1.1/friendships/create.json', post('user_id=abc')],
['/i/api/1.1/friendships/create.json', post('screen_name=jack')],
['/i/api/1.1/friendships/create.json', post('user_id=1&user_id=2')],
['/i/api/1.1/friendships/create.json', post(new FormData())],
['https://api.x.com/1.1/friendships/create.json', post('user_id=1')],
['https://evil.example/i/api/1.1/friendships/create.json', post('user_id=1')],
[new Request('https://x.com/i/api/1.1/friendships/create.json', { method: 'POST', body: 'user_id=1' }), undefined],
['http://[bad', post('user_id=1')],
[42, post('user_id=1')],
];
for (const [input, init] of none) assert.equal(matchMutation(input, init, BASE), null, `${String(input)} ${String(init?.body)}`);
});
test('classifier: confirmed, requested and unknown outcomes', () => {
const like: MutationMatch = { kind: 'like', id: '1', key: 'tweet:1', want: true };
const unlike: MutationMatch = { ...like, want: false };
const follow: MutationMatch = { kind: 'follow', id: '9', key: 'user:9', want: true };
const unfollow: MutationMatch = { ...follow, want: false };
assert.deepEqual(classifyResponse(like, 200, '{"data":{"favorite_tweet":"Done"}}'), { outcome: 'confirmed', state: true });
assert.deepEqual(classifyResponse(unlike, 200, '{"data":{"unfavorite_tweet":"Done"}}'), { outcome: 'confirmed', state: false });
assert.deepEqual(classifyResponse(like, 200, '{"errors":[{"code":139}],"data":{}}'), { outcome: 'confirmed', state: true });
assert.deepEqual(classifyResponse(unlike, 200, '{"errors":[{"code":139}]}'), { outcome: 'unknown' });
assert.deepEqual(classifyResponse(like, 429, '{"errors":[{"code":88}]}'), { outcome: 'unknown' });
assert.deepEqual(classifyResponse(like, 500, 'oops'), { outcome: 'unknown' });
assert.deepEqual(classifyResponse(like, 200, '{"data":{"favorite_tweet":"Nope"}}'), { outcome: 'unknown' });
assert.deepEqual(classifyResponse(follow, 200, '{"id_str":"9","following":false}'), { outcome: 'confirmed', state: true });
assert.deepEqual(classifyResponse(follow, 200, '{"id_str":"9","protected":true}'), { outcome: 'requested' });
assert.deepEqual(classifyResponse(follow, 200, '{"id_str":"9","follow_request_sent":true}'), { outcome: 'requested' });
assert.deepEqual(classifyResponse(unfollow, 200, '{"id_str":"9"}'), { outcome: 'confirmed', state: false });
assert.deepEqual(classifyResponse(follow, 403, '{"errors":[{"code":161}]}'), { outcome: 'unknown' });
assert.deepEqual(classifyResponse(follow, 200, '[]'), { outcome: 'unknown' });
});
test('synthesized bodies: like Done, follow clones the last user object', () => {
assert.equal(synthesizedBody({ kind: 'like', id: '1', key: 'tweet:1', want: true }, undefined), '{"data":{"favorite_tweet":"Done"}}');
assert.equal(synthesizedBody({ kind: 'like', id: '1', key: 'tweet:1', want: false }, undefined), '{"data":{"unfavorite_tweet":"Done"}}');
const follow: MutationMatch = { kind: 'follow', id: '9', key: 'user:9', want: false };
assert.deepEqual(JSON.parse(synthesizedBody(follow, '{"id_str":"9","screen_name":"a","following":true}')), { id_str: '9', screen_name: 'a', following: false });
assert.deepEqual(JSON.parse(synthesizedBody(follow, '{"id_str":"8"}')), { id: 9, id_str: '9', following: false });
assert.deepEqual(JSON.parse(synthesizedBody(follow, 'garbage')), { id: 9, id_str: '9', following: false });
});
// ---------- lane FSM ----------
type Result = { kind: 'wire' | 'synthetic'; want: boolean; n?: number };
type Wire = { n: number; key: string; want: boolean; resolve(outcome: 'ok' | 'unknown' | 'reject'): void };
function harness() {
const lanes = createLanes<Result>();
const wire: Wire[] = [];
const open = new Map<string, number>();
let maxConcurrent = 0;
let n = 0;
const click = (key: string, want: boolean, signal?: AbortSignal) => {
const id = n++;
const req: LaneRequest<Result> = {
want,
signal,
send: () => new Promise<Result>((resolve, reject) => {
open.set(key, (open.get(key) ?? 0) + 1);
maxConcurrent = Math.max(maxConcurrent, open.get(key)!);
wire.push({ n: id, key, want, resolve: outcome => {
open.set(key, open.get(key)! - 1);
if (outcome === 'reject') reject(new TypeError('Failed to fetch'));
else resolve({ kind: 'wire', want, n: outcome === 'ok' ? id : -1 });
} });
}),
classify: (result): Promise<Settled> => Promise.resolve(result.n === -1 ? { outcome: 'unknown' } : { outcome: 'confirmed', state: result.want }),
synthesize: () => ({ kind: 'synthetic', want }),
};
return lanes.submit(key, req);
};
return { lanes, wire, click, get maxConcurrent() { return maxConcurrent; } };
}
test('lane table: idle sends; busy holds; same direction shares; opposite pair-cancels', async () => {
const h = harness();
const a = h.click('t', true); // idle -> wire
const b = h.click('t', false); // busy -> held
const c = h.click('t', true); // opposite of held -> pair cancel b+c
assert.equal(h.wire.length, 1);
const d = h.click('t', false); // busy -> held again
const e = h.click('t', false); // same as held -> shares
assert.deepEqual(h.lanes.stats, { sent: 1, held: 2, shared: 1, paired: 1, synthesized: 0, cancelled: 0 });
h.wire[0]!.resolve('ok');
assert.deepEqual(await a, { kind: 'wire', want: true, n: 0 });
assert.deepEqual(await b, { kind: 'synthetic', want: false });
assert.deepEqual(await c, { kind: 'synthetic', want: true });
await tick();
assert.equal(h.wire.length, 2); // d released exactly once; e waits behind it
assert.equal(h.wire[1]!.n, 3);
h.wire[1]!.resolve('ok');
assert.deepEqual(await d, { kind: 'wire', want: false, n: 3 });
assert.deepEqual(await e, { kind: 'synthetic', want: false });
assert.equal(h.lanes.size(), 0);
assert.equal(h.maxConcurrent, 1);
});
test('lane table: pending matching confirmed state is satisfied without a request', async () => {
const h = harness();
const a = h.click('t', true);
const b = h.click('t', true); // stale duplicate like
h.wire[0]!.resolve('ok');
await a;
assert.deepEqual(await b, { kind: 'synthetic', want: true });
assert.equal(h.wire.length, 1);
});
test('lane table: unknown outcome and rejection release the held request as-is', async () => {
const h = harness();
const a = h.click('t', true);
const b = h.click('t', true);
h.wire[0]!.resolve('unknown');
assert.deepEqual(await a, { kind: 'wire', want: true, n: -1 });
await tick();
assert.equal(h.wire.length, 2); // not synthesized: X's own behavior stands
const c = h.click('t', false);
h.wire[1]!.resolve('reject');
await assert.rejects(b, TypeError); // rejection reaches X unchanged
await tick();
assert.equal(h.wire.length, 3);
h.wire[2]!.resolve('ok');
assert.deepEqual(await c, { kind: 'wire', want: false, n: 2 });
});
test('lane table: aborting a held request rejects it and it never reaches the wire', async () => {
const h = harness();
const a = h.click('t', true);
const controller = new AbortController();
const b = h.click('t', false, controller.signal);
controller.abort();
await assert.rejects(b, (error: unknown) => (error as Error).name === 'AbortError');
h.wire[0]!.resolve('ok');
await a;
await tick();
assert.equal(h.wire.length, 1);
assert.equal(h.lanes.size(), 0);
});
test('lane table: separate targets never block each other', () => {
const h = harness();
void h.click('t1', true);
void h.click('t2', true);
void h.click('u1', false);
assert.equal(h.wire.length, 3);
});
function rng(seed: number) {
return () => { seed = (seed * 1_103_515_245 + 12_345) & 0x7fffffff; return seed / 0x7fffffff; };
}
test('property: wire ⊆ naive in click order, never concurrent, final state == last intent', async () => {
for (let seed = 1; seed <= 300; seed++) {
const random = rng(seed);
const h = harness();
const keys = ['a', 'b', 'c'];
const intent = new Map<string, boolean>(); // X's optimistic view: each click toggles
const server = new Map<string, boolean>();
const naive: { n: number; key: string; want: boolean }[] = [];
const failures = random() < 0.3;
const results: Promise<unknown>[] = [];
const done = new Set<Wire>();
const settleOne = () => {
const openWire = h.wire.filter(w => !done.has(w));
if (!openWire.length) return;
const w = openWire[Math.floor(random() * openWire.length)]!;
done.add(w);
const roll = random();
const outcome = failures && roll < 0.2 ? 'unknown' : failures && roll < 0.3 ? 'reject' : 'ok';
if (outcome === 'ok') server.set(w.key, w.want);
w.resolve(outcome);
};
for (let step = 0; step < 40; step++) {
if (random() < 0.6) {
const key = keys[Math.floor(random() * keys.length)]!;
const want = !(intent.get(key) ?? false);
intent.set(key, want);
naive.push({ n: naive.length, key, want });
results.push(h.click(key, want).catch(() => {}));
} else settleOne();
await tick();
}
while (h.wire.length > done.size) { settleOne(); await tick(); }
await Promise.all(results);
assert.equal(h.maxConcurrent <= 1, true, `seed ${seed}: concurrent`);
// Every wire request is one of X's own, sent in click order per target.
for (const key of keys) {
const sent = h.wire.filter(w => w.key === key).map(w => w.n);
assert.deepEqual(sent, [...sent].sort((x, y) => x - y), `seed ${seed}: order`);
for (const n of sent) assert.equal(naive[n]!.key, key);
for (const w of h.wire) assert.equal(naive[w.n]!.want, w.want);
if (!failures && intent.has(key) && naive.some(c => c.key === key)) {
// With no failures the confirmed server state equals the last click intent.
assert.equal(server.get(key) ?? false, intent.get(key), `seed ${seed}: final ${key}`);
}
}
assert.equal(new Set(h.wire.map(w => w.n)).size, h.wire.length, `seed ${seed}: sent twice`);
assert.ok(h.wire.length <= naive.length);
assert.equal(h.lanes.size(), 0, `seed ${seed}: lanes drained`);
}
});
// ---------- fetch gate ----------
function fakeWindow(respond: (input: unknown, init?: RequestInit) => Promise<Response>) {
const calls: { input: unknown; init?: RequestInit }[] = [];
const original = function (input: unknown, init?: RequestInit) { calls.push({ input, init }); return respond(input, init); } as typeof fetch;
return { win: { fetch: original, location: { href: BASE } }, original, calls };
}
const json = (body: unknown, status = 200) => new Response(JSON.stringify(body), { status, headers: { 'content-type': 'application/json' } });
test('gate: unmatched requests return the original promise; rejections pass through', async () => {
const failure = Promise.reject(new TypeError('Failed to fetch'));
failure.catch(() => {});
const { win, calls } = fakeWindow((input, init) => String(input).includes('fail') || String(init?.body).includes('666') ? failure : Promise.resolve(json({})));
const gate = installMutationGate(win);
assert.equal(gate.installed, true);
assert.equal(win.fetch('/i/api/fail'), failure);
await assert.rejects(win.fetch('/i/api/graphql/x/FavoriteTweet', post(likeBody('666'))));
assert.equal(calls.length, 2);
gate.dispose();
});
test('gate: burst like/unlike/like on one tweet sends one request and resolves every caller', async () => {
let release!: () => void;
const gateOpen = new Promise<void>(resolve => { release = resolve; });
const { win, calls, original } = fakeWindow(async () => { await gateOpen; return json({ data: { favorite_tweet: 'Done' } }); });
const gate = installMutationGate(win);
const url = '/i/api/graphql/lI07N6Otwv1PhnEgXILM7A/FavoriteTweet';
const unurl = '/i/api/graphql/ZYKSe-w7KEslx3JhSIk5LA/UnfavoriteTweet';
const a = win.fetch(url, post(likeBody('5')));
const b = win.fetch(unurl, post(likeBody('5')));
const c = win.fetch(url, post(likeBody('5')));
assert.equal(calls.length, 1);
release();
assert.deepEqual(await (await a).json(), { data: { favorite_tweet: 'Done' } });
assert.deepEqual(await (await b).json(), { data: { unfavorite_tweet: 'Done' } });
assert.deepEqual(await (await c).json(), { data: { favorite_tweet: 'Done' } });
assert.equal(calls.length, 1);
gate.dispose();
assert.equal(win.fetch, original);
});
test('gate: kill switch leaves a captured wrapper as pure pass-through', async () => {
const { win, calls } = fakeWindow(() => new Promise(() => {}));
const gate = installMutationGate(win);
const captured = win.fetch;
const wrapped = { fetch: captured }; // e.g. another wrapper installed on top
gate.dispose();
void wrapped.fetch('/i/api/graphql/x/FavoriteTweet', post(likeBody('1')));
void wrapped.fetch('/i/api/graphql/x/FavoriteTweet', post(likeBody('1')));
assert.equal(calls.length, 2);
});
test('gate: composes with an earlier fetch wrapper instead of replacing it', async () => {
const { win, original } = fakeWindow(() => Promise.resolve(json({ data: { favorite_tweet: 'Done' } })));
const seen: unknown[] = [];
const observer = function (this: unknown, ...args: Parameters<typeof fetch>) { seen.push(args[0]); return Reflect.apply(original, this, args); } as typeof fetch;
win.fetch = observer;
const gate = installMutationGate(win);
await win.fetch('/i/api/graphql/x/FavoriteTweet', post(likeBody('1')));
assert.equal(seen.length, 1);
gate.dispose();
assert.equal(win.fetch, observer);
});
// ---------- click guard: see tests/clickGuard.test.ts (held clicks are delivered, never dropped) ----------
// ---------- XHR transport (X's logged-in client sends these mutations with XMLHttpRequest) ----------
type XhrWire = { name: string; id: string };
function xhrEnv(liked: boolean) {
const server = { liked, wire: [] as XhrWire[], inflight: new Map<string, number>(), concurrent: false };
const replies: (() => void)[] = [];
class FakeXHR extends EventTarget {
#s = { readyState: 0, status: 0, text: '', method: '', url: '' };
responseType: XMLHttpRequestResponseType = '';
get readyState() { return this.#s.readyState; }
get status() { return this.#s.status; }
get responseText() { return this.#s.text; }
get response() { return this.#s.text; }
get responseURL() { return this.#s.readyState === 4 ? `https://x.com${this.#s.url}` : ''; }
getResponseHeader() { return 'application/json'; }
getAllResponseHeaders() { return 'content-type: application/json\r\n'; }
open(method: string, url: string) { Object.assign(this.#s, { method, url, readyState: 1, status: 0, text: '' }); }
send(body: string) {
const name = this.#s.url.split('/').pop()!;
const id = String(JSON.parse(body).variables.tweet_id);
const n = (server.inflight.get(id) ?? 0) + 1;
server.inflight.set(id, n);
if (n > 1) server.concurrent = true;
server.wire.push({ name, id });
replies.push(() => {
server.inflight.set(id, server.inflight.get(id)! - 1);
const want = name === 'FavoriteTweet';
let text: string;
if (want && server.liked) text = '{"data":{},"errors":[{"code":139,"message":"already favorited"}]}';
else { server.liked = want; text = want ? '{"data":{"favorite_tweet":"Done"}}' : '{"data":{"unfavorite_tweet":"Done"}}'; }
Object.assign(this.#s, { readyState: 4, status: 200, text });
for (const type of ['readystatechange', 'load', 'loadend']) this.dispatchEvent(new Event(type));
});
}
abort() {
Object.assign(this.#s, { readyState: 4, status: 0 });
for (const type of ['readystatechange', 'abort', 'loadend']) this.dispatchEvent(new Event(type));
this.#s.readyState = 0;
}
}
const win = { fetch: (async () => json({})) as typeof fetch, location: { href: BASE }, XMLHttpRequest: FakeXHR as unknown as typeof XMLHttpRequest };
// X's click handler: sends the opposite of the current UI state and flips the UI optimistically.
let ui = liked;
const results: { xhr: XMLHttpRequest; events: string[] }[] = [];
const click = (id = '5') => {
const xhr = new win.XMLHttpRequest();
const events: string[] = [];
for (const type of ['readystatechange', 'load', 'abort', 'loadend']) xhr.addEventListener(type, () => events.push(type === 'readystatechange' ? `rs${xhr.readyState}` : type));
xhr.open('POST', `/i/api/graphql/q/${ui ? 'UnfavoriteTweet' : 'FavoriteTweet'}`);
xhr.send(likeBody(id));
ui = !ui;
results.push({ xhr, events });
return xhr;
};
return { win, server, replies, click, results, get ui() { return ui; } };
}
test('gate (XHR): 7 like/unlike clicks with out-of-order completion: wire ⊆ naive, final server == last intent', async () => {
const env = xhrEnv(true);
const gate = installMutationGate(env.win);
for (let i = 0; i < 7; i++) { env.click(); await tick(); }
// Naive X sends 7 concurrent requests; the lane sends the first and pairs the rest away.
assert.deepEqual(env.server.wire.map(w => w.name), ['UnfavoriteTweet']);
// Complete whatever is on the wire, newest first (out of order).
while (env.replies.length) { env.replies.pop()!(); await tick(); }
await tick();
assert.equal(env.server.liked, env.ui, 'server state equals the last click intent');
assert.equal(env.server.concurrent, false);
for (const { xhr, events } of env.results) {
assert.equal(xhr.readyState, 4);
assert.equal(xhr.status, 200);
assert.equal(events.filter(e => e === 'loadend').length, 1, 'every X request completes exactly once');
assert.ok(JSON.parse(xhr.responseText).data, 'with a success body of its own shape');
}
const held = env.results[1]!;
assert.deepEqual(held.events, ['rs2', 'rs3', 'rs4', 'load', 'loadend'], 'browser event order for a synthesized completion');
assert.deepEqual(JSON.parse(held.xhr.responseText), { data: { favorite_tweet: 'Done' } });
assert.equal(held.xhr.getResponseHeader('Content-Type'), 'application/json; charset=utf-8');
assert.equal(gate.lanes.size(), 0);
gate.dispose();
});
test('gate (XHR): randomized bursts keep wire ⊆ naive, never concurrent, final == last intent', async () => {
for (let seed = 1; seed <= 40; seed++) {
const random = rng(seed);
const env = xhrEnv(random() < 0.5);
const gate = installMutationGate(env.win);
const clicks = 1 + Math.floor(random() * 8);
for (let i = 0; i < clicks; i++) {
env.click();
await tick();
// Sometimes the server answers an in-flight request before the next click, in any order.
while (env.replies.length && random() < 0.4) { env.replies.splice(Math.floor(random() * env.replies.length), 1)[0]!(); await tick(); }
}
while (env.replies.length) { env.replies.splice(Math.floor(random() * env.replies.length), 1)[0]!(); await tick(); }
await tick();
assert.equal(env.server.concurrent, false, `seed ${seed}: concurrent`);
assert.ok(env.server.wire.length <= clicks, `seed ${seed}: wire ⊆ naive`);
assert.equal(env.server.liked, env.ui, `seed ${seed}: final state`);
for (const { events } of env.results) assert.equal(events.filter(e => e === 'loadend').length, 1, `seed ${seed}: one loadend each`);
assert.equal(gate.lanes.size(), 0, `seed ${seed}: lanes drained`);
gate.dispose();
}
});
test('gate (XHR): abort of a held request never reaches the wire; unmatched XHRs are untouched', async () => {
const env = xhrEnv(false);
const nativeSend = env.win.XMLHttpRequest.prototype.send;
const gate = installMutationGate(env.win);
env.click(); // Favorite: on the wire
const held = env.click(); // Unfavorite: held behind it
held.abort();
await tick();
assert.deepEqual(env.results[1]!.events, ['rs4', 'abort', 'loadend']);
assert.equal(held.readyState, 0);
env.replies.shift()!();
await tick();
assert.deepEqual(env.server.wire.map(w => w.name), ['FavoriteTweet']);
// Reuse after abort: the object behaves natively again.
held.open('POST', '/i/api/graphql/q/UnfavoriteTweet');
assert.equal(held.readyState, 1);
// A non-mutation XHR goes straight to the native send (the fake parses its body and throws on null).
const plain = new env.win.XMLHttpRequest();
plain.open('GET', '/i/api/2/badge_count/badge_count.json');
assert.throws(() => plain.send(null));
assert.notEqual(env.win.XMLHttpRequest.prototype.send, nativeSend, 'hooked while installed');
gate.dispose();
assert.equal(env.win.XMLHttpRequest.prototype.send, nativeSend, 'dispose restores the native send');
});
test('feature flag: fixLikeFollow defaults on and is one of the Fixes', () => {
assert.equal(FEATURE_DEFAULTS.fixLikeFollow, true);
// A core flag: owned by no plugin, so no plugin switch can turn it off (src/areas/table.ts).
assert.deepEqual(X_FEATURES.filter(feature => feature.area === null && feature.group === 'fixes').map(feature => feature.id),
['fixLikeFollow', 'fixScrollRestore', 'fixTimelineJump', 'fixHomeTab', 'fixNotificationBadge', 'fixNewPostsPill', 'fixMediaUpload', 'followingChronological', 'fixChatButton', 'composerRestorePrompt']);
for (const key of ['fixScrollRestore', 'fixTimelineJump', 'fixHomeTab', 'fixNotificationBadge', 'fixNewPostsPill', 'fixMediaUpload', 'followingChronological', 'fixChatButton', 'composerRestorePrompt'] as const) {
assert.equal(FEATURE_DEFAULTS[key], true, key);
}
});
// ---------- failed in-flight request: nothing unconfirmed is ever answered with a success ----------
type XResult = { kind: 'wire' | 'synthetic'; ok: boolean; n: number };
/**
* Lane harness with X's view of each caller: a wire result with ok=false (429, 5xx) or a rejection
* (network, abort) makes X revert that click's optimistic change; a success commits it. X's UI ends
* on the intent of the last committed click (redux-optimist replay of absolute `following`/`favorited`).
*/
function failHarness(initial = false) {
const lanes = createLanes<XResult>();
const wire: { n: number; want: boolean; settle(outcome: 'ok' | 'fail' | 'odd' | 'reject' | 'abort'): void }[] = [];
const clicks: { want: boolean; status: 'pending' | 'committed' | 'reverted'; how?: string }[] = [];
let server = initial;
let open = 0, maxOpen = 0;
const click = (want: boolean) => {
const n = clicks.length;
const entry: { want: boolean; status: 'pending' | 'committed' | 'reverted'; how?: string } = { want, status: 'pending' };
clicks.push(entry);
const promise = lanes.submit('user:9', {
want,
send: () => new Promise<XResult>((resolve, reject) => {
open++; maxOpen = Math.max(maxOpen, open);
wire.push({ n, want, settle: outcome => {
open--;
if (outcome === 'ok') { server = want; resolve({ kind: 'wire', ok: true, n }); }
else if (outcome === 'odd') resolve({ kind: 'wire', ok: true, n: -1 }); // a 200 the lane cannot read
else if (outcome === 'fail') resolve({ kind: 'wire', ok: false, n });
else reject(outcome === 'abort' ? new DOMException('The operation was aborted.', 'AbortError') : new TypeError('Failed to fetch'));
} });
}),
classify: (result): Promise<Settled> => Promise.resolve(result.ok && result.n >= 0 ? { outcome: 'confirmed', state: want } : { outcome: 'unknown' }),
synthesize: () => ({ kind: 'synthetic', ok: true, n }),
});
promise.then(result => { entry.status = result.ok ? 'committed' : 'reverted'; entry.how = result.kind; },
error => { entry.status = 'reverted'; entry.how = (error as Error).name; });
return promise.catch(() => undefined);
};
const ui = () => [...clicks].reverse().find(c => c.status === 'committed')?.want ?? initial;
return { lanes, wire, clicks, click, ui, get server() { return server; }, get maxOpen() { return maxOpen; } };
}
test('lanes: follow (429) + unfollow + follow while it is in flight: no synthesized success, the last follow is sent', async () => {
const h = failHarness(false);
const a = h.click(true);
const b = h.click(false); // held (after the confirm sheet)
const c = h.click(true); // pairs with the held unfollow
h.wire[0]!.settle('fail'); // HTTP 429
await Promise.all([a, b]);
await tick();
assert.equal(h.clicks[1]!.how, 'AbortError', 'the unfollow fails like an aborted request: X reverts it');
assert.equal(h.wire.length, 2, 'the latest intent goes out once, as X made it');
assert.deepEqual([h.wire[1]!.n, h.wire[1]!.want], [2, true]);
assert.equal(h.lanes.stats.synthesized, 0);
h.wire[1]!.settle('ok');
await c;
assert.equal(h.clicks[2]!.how, 'wire');
assert.equal(h.server, true);
assert.equal(h.ui(), h.server);
await tick();
assert.equal(h.lanes.size(), 0);
});
test('lanes: follow (429) + unfollow + follow, and the released follow is rate-limited too', async () => {
const h = failHarness(false);
const all = [h.click(true), h.click(false), h.click(true)];
h.wire[0]!.settle('fail');
await tick(); await tick();
h.wire[1]!.settle('fail');
await Promise.all(all);
assert.equal(h.server, false);
assert.equal(h.ui(), false, 'every optimistic change reverted: X shows the real state');
assert.equal(h.lanes.stats.synthesized, 0);
});
test('lanes: like (500) + held unlike: the unlike is sent for real, never answered by the lane', async () => {
const h = failHarness(false);
const a = h.click(true);
const b = h.click(false);
h.wire[0]!.settle('fail'); // HTTP 500
await a;
await tick();
assert.equal(h.wire.length, 2);
assert.deepEqual([h.wire[1]!.n, h.wire[1]!.want], [1, false]);
h.wire[1]!.settle('ok');
await b;
assert.equal(h.clicks[1]!.how, 'wire');
assert.equal(h.ui(), h.server);
assert.equal(h.lanes.stats.synthesized, 0);
});
test('lanes: X aborts the in-flight request: held pair fails, the latest intent is released', async () => {
const h = failHarness(true);
const all = [h.click(false), h.click(true), h.click(false)];
h.wire[0]!.settle('abort');
await tick(); await tick();
assert.equal(h.clicks[1]!.how, 'AbortError');
assert.equal(h.wire.length, 2);
assert.equal(h.wire[1]!.n, 2);
h.wire[1]!.settle('ok');
await Promise.all(all);
assert.equal(h.server, false);
assert.equal(h.ui(), false);
assert.equal(h.lanes.stats.synthesized, 0);
});
test('lanes: unknown outcome (a 200 the lane cannot read) confirms nothing', async () => {
const h = failHarness(false);
const all = [h.click(true), h.click(false), h.click(true), h.click(true)];
h.wire[0]!.settle('odd');
await tick(); await tick();
assert.equal(h.clicks[1]!.how, 'AbortError');
assert.equal(h.wire.length, 2, 'one follow of the trailing run is released, the other waits behind it');
assert.equal(h.wire[1]!.n, 2);
h.wire[1]!.settle('ok');
await Promise.all(all);
assert.equal(h.clicks[3]!.how, 'synthetic', 'the stale duplicate is satisfied by the confirmed follow');
assert.equal(h.wire.length, 2);
assert.equal(h.ui(), true);
assert.equal(h.server, true);
});
test('lanes: a held click in the same direction as the in-flight one no longer swallows a later reversal', async () => {
const h = failHarness(false);
const all = [h.click(true), h.click(true), h.click(false)]; // stale double follow, then unfollow
h.wire[0]!.settle('ok');
await tick(); await tick();
assert.equal(h.clicks[1]!.how, 'synthetic', 'already satisfied by the confirmed follow');
assert.equal(h.wire.length, 2, 'the unfollow is sent');
h.wire[1]!.settle('ok');
await Promise.all(all);
assert.equal(h.server, false);
assert.equal(h.ui(), false);
});
test('property: with failures the final UI equals the server; each request sent once, in click order, never concurrent', async () => {
for (let seed = 1; seed <= 400; seed++) {
const random = rng(seed);
const h = failHarness(random() < 0.5);
const results: Promise<unknown>[] = [];
let intent = h.ui();
const settled = new Set<number>();
const settleOne = () => {
const index = h.wire.findIndex((_, i) => !settled.has(i));
if (index < 0) return;
settled.add(index);
const roll = random();
h.wire[index]!.settle(roll < 0.2 ? 'fail' : roll < 0.3 ? 'reject' : roll < 0.35 ? 'abort' : 'ok');
};
for (let step = 0; step < 30; step++) {
if (random() < 0.6) { intent = !intent; results.push(h.click(intent)); }
else settleOne();
await tick();
}
while (h.wire.length > settled.size) { settleOne(); await tick(); }
await Promise.all(results);
assert.equal(h.ui(), h.server, `seed ${seed}: UI differs from the server`);
assert.ok(h.maxOpen <= 1, `seed ${seed}: concurrent`);
const ns = h.wire.map(w => w.n);
assert.deepEqual(ns, [...ns].sort((x, y) => x - y), `seed ${seed}: order`);
assert.equal(new Set(ns).size, ns.length, `seed ${seed}: sent twice`);
for (const w of h.wire) assert.equal(h.clicks[w.n]!.want, w.want);
assert.equal(h.lanes.size(), 0, `seed ${seed}: drained`);
}
});