mirror of
https://github.com/fluxerapp/fluxer
synced 2026-10-19 05:55:34 +09:00
chore(repo): remove dead code and useless tests (#3375)
This commit is contained in:
1 parent
7e177a1c90
commit
1cd62eb661
1391 files changed
+783
-120415
No files matched your search
@@ -11,32 +11,19 @@ import {
|
||||
import {SYSTEM_USER_ID} from '@app/api/constants/Core';
|
||||
import type {UserRow} from '@app/api/database/types/UserTypes';
|
||||
import {EMPTY_USER_ROW} from '@app/api/database/types/UserTypes';
|
||||
import {
|
||||
ACTIONS_STREAM,
|
||||
type ActionEnvelope,
|
||||
type ActionOutcome,
|
||||
effectsConsumer,
|
||||
} from '@app/api/infrastructure/activity/Contract.generated';
|
||||
import type {ActionEnvelope, ActionOutcome} from '@app/api/infrastructure/activity/Contract.generated';
|
||||
import {User} from '@app/api/models/User';
|
||||
import {NEW_CONVERSATION_LIMIT_MAX_MS} from '@app/api/user/NewConversationLimit';
|
||||
import type {AccountStateDeps} from '@app/api/user/services/AccountStateApplier';
|
||||
import {
|
||||
type AccountActionDeps,
|
||||
applyAction,
|
||||
handleActionMessage,
|
||||
startAccountActionConsumer,
|
||||
stopAccountActionConsumer,
|
||||
} from '@app/api/worker/AccountActionConsumer';
|
||||
import {type AccountActionDeps, applyAction, handleActionMessage} from '@app/api/worker/AccountActionConsumer';
|
||||
import {UserFlags} from '@fluxer/constants/src/UserConstants';
|
||||
import type {AdminArchiveResponse} from '@fluxer/schema/src/domains/admin/AdminArchiveSchemas';
|
||||
import {createSnowflakeFromTimestamp} from '@fluxer/snowflake/src/Snowflake';
|
||||
import {AckPolicy, DeliverPolicy, type JsMsg, jetstream, jetstreamManager} from '@nats-io/jetstream';
|
||||
import {connect} from '@nats-io/transport-node';
|
||||
import {afterAll, beforeAll, beforeEach, describe, expect, it} from 'vitest';
|
||||
import type {JsMsg} from '@nats-io/jetstream';
|
||||
import {beforeEach, describe, expect, it} from 'vitest';
|
||||
|
||||
const USER_ID = '1174109840998400001';
|
||||
const NOW = 1_759_000_000_000;
|
||||
const NATS_URL = process.env.FLUXER_TEST_ACTIVITY_NATS_URL;
|
||||
const DAY_MS = 86_400_000;
|
||||
const DELETION_DELAY_MS = 7 * DAY_MS;
|
||||
|
||||
@@ -740,59 +727,3 @@ describe('account action messages', () => {
|
||||
expect(last.state.acked).toBe(1);
|
||||
});
|
||||
});
|
||||
|
||||
describe.skipIf(!NATS_URL)('account action consumer against JetStream', () => {
|
||||
let nc: Awaited<ReturnType<typeof connect>>;
|
||||
beforeAll(async () => {
|
||||
nc = await connect({servers: NATS_URL, token: process.env.FLUXER_TEST_ACTIVITY_NATS_TOKEN});
|
||||
const jsm = await jetstreamManager(nc);
|
||||
await jsm.streams.delete(ACTIONS_STREAM).catch(() => undefined);
|
||||
await jsm.streams.add({name: ACTIONS_STREAM, subjects: ['act.*']});
|
||||
for (const partition of [0, 7]) {
|
||||
await jsm.consumers.add(ACTIONS_STREAM, {
|
||||
durable_name: effectsConsumer(partition),
|
||||
filter_subject: `act.${String(partition).padStart(2, '0')}`,
|
||||
ack_policy: AckPolicy.Explicit,
|
||||
max_ack_pending: 1,
|
||||
deliver_policy: DeliverPolicy.All,
|
||||
});
|
||||
}
|
||||
});
|
||||
afterAll(async () => {
|
||||
await stopAccountActionConsumer();
|
||||
await nc?.close();
|
||||
});
|
||||
|
||||
it('applies each action in order and acknowledges it after the outcome', async () => {
|
||||
const h = harness();
|
||||
h.users.put();
|
||||
h.deps.now = Date.now;
|
||||
const outcomes: Array<ActionOutcome> = [];
|
||||
h.deps.publishOutcome = async (_key, outcome) => {
|
||||
outcomes.push(outcome);
|
||||
};
|
||||
h.deps.js = jetstream(nc);
|
||||
h.deps.retryDelayMs = 200;
|
||||
const js = jetstream(nc);
|
||||
const expires = Date.now() + 60_000;
|
||||
await js.publish('act.07', JSON.stringify(limitEnvelope({id: 'a:07:1:0', expires_at_ms: expires})), {
|
||||
msgID: 'a:07:1:0',
|
||||
});
|
||||
await js.publish('act.07', JSON.stringify(limitEnvelope({id: 'a:07:2:0', on: false, expires_at_ms: expires})), {
|
||||
msgID: 'a:07:2:0',
|
||||
});
|
||||
startAccountActionConsumer(h.deps);
|
||||
const deadline = Date.now() + 10_000;
|
||||
while (outcomes.length < 2 && Date.now() < deadline) {
|
||||
await new Promise((resolve) => setTimeout(resolve, 50));
|
||||
}
|
||||
expect(outcomes.map((outcome) => [outcome.action_id, outcome.status])).toEqual([
|
||||
['a:07:1:0', 'applied'],
|
||||
['a:07:2:0', 'applied'],
|
||||
]);
|
||||
expect(h.users.current().flags).toBe(0n);
|
||||
const info = await (await jetstreamManager(nc)).consumers.info(ACTIONS_STREAM, effectsConsumer(7));
|
||||
expect(info.num_ack_pending).toBe(0);
|
||||
expect(info.num_pending).toBe(0);
|
||||
});
|
||||
});
|
||||
@@ -1,25 +0,0 @@
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
import {createArchiveJsonBuffer, stringifyArchiveJson} from '@app/api/worker/utils/ArchiveJson';
|
||||
import {describe, expect, test} from 'vitest';
|
||||
|
||||
describe('ArchiveJson', () => {
|
||||
test('serializes non-ASCII text as JSON escapes while preserving parsed values', () => {
|
||||
const guildName = '\u00c61';
|
||||
const emoji = '\ud83d\ude00';
|
||||
const json = stringifyArchiveJson({guild_name: guildName, emoji});
|
||||
|
||||
expect(json).toContain('"guild_name": "\\u00c61"');
|
||||
expect(json).toContain('"emoji": "\\ud83d\\ude00"');
|
||||
expect(json).not.toContain('\u00c3');
|
||||
expect(JSON.parse(json)).toEqual({guild_name: guildName, emoji});
|
||||
});
|
||||
|
||||
test('creates ASCII-only UTF-8 buffers for archive entries', () => {
|
||||
const guildName = '\u00c61';
|
||||
const buffer = createArchiveJsonBuffer({guild_name: guildName});
|
||||
|
||||
expect(buffer.equals(Buffer.from(buffer.toString('ascii'), 'ascii'))).toBe(true);
|
||||
expect(JSON.parse(buffer.toString('utf8'))).toEqual({guild_name: guildName});
|
||||
});
|
||||
});
|
||||
@@ -121,7 +121,7 @@ describe('blocklist feeds turned off', () => {
|
||||
await syncFileShaBlocklists({}, createHelpers());
|
||||
|
||||
staleRead.mockRestore();
|
||||
expect(await adminRepository.isFileShaBanned(FEED_SHA)).toBe(true);
|
||||
expect((await adminRepository.loadAllBannedFileShas()).map((row) => row.sha256_hex)).toContain(FEED_SHA);
|
||||
});
|
||||
|
||||
it('file-SHA cache skips feed rows only while feeds are off', async () => {
|
||||
|
||||
@@ -182,6 +182,6 @@ WHERE table_name = 'banned_file_shas' AND row_data->>'sha256_hex' = $1`,
|
||||
await syncFileShaBlocklists({}, createHelpers());
|
||||
|
||||
staleRead.mockRestore();
|
||||
expect(await adminRepository.isFileShaBanned(FEED_SHA)).toBe(true);
|
||||
expect((await adminRepository.loadAllBannedFileShas()).map((row) => row.sha256_hex)).toContain(FEED_SHA);
|
||||
});
|
||||
});
|
||||
@@ -589,7 +589,6 @@ describe('bulkScheduleUserDeletion', () => {
|
||||
return 1n;
|
||||
},
|
||||
cancelJob: async () => false,
|
||||
retryDeadLetterJob: async () => false,
|
||||
});
|
||||
await createBuilder(harness, admin.token)
|
||||
.post('/admin/bulk-jobs')
|
||||
|
||||
@@ -15,24 +15,12 @@ import {
|
||||
} from '@app/api/channel/tests/CrosspostWorkerTestUtils';
|
||||
import {type ApiTestHarness, createApiTestHarness} from '@app/api/test/ApiTestHarness';
|
||||
import {NoopGatewayService} from '@app/api/test/NoopGatewayService';
|
||||
import {SyncTaskWorkerService} from '@app/api/test/SyncTaskWorkerService';
|
||||
import {WebhookRepository} from '@app/api/webhook/WebhookRepository';
|
||||
import crosspostMessage from '@app/api/worker/tasks/CrosspostMessage';
|
||||
import crosspostMessageChunk from '@app/api/worker/tasks/CrosspostMessageChunk';
|
||||
import removeChannelFollowers from '@app/api/worker/tasks/RemoveChannelFollowers';
|
||||
import syncCrosspostCopies from '@app/api/worker/tasks/SyncCrosspostCopies';
|
||||
import syncCrosspostedMessage from '@app/api/worker/tasks/SyncCrosspostedMessage';
|
||||
import {
|
||||
findLaneForTask,
|
||||
resolveWorkerLanes,
|
||||
validateLaneCompleteness,
|
||||
WORKER_LANES,
|
||||
} from '@app/api/worker/WorkerLaneConfig';
|
||||
import {workerTasks} from '@app/api/worker/WorkerTaskRegistry';
|
||||
import {ChannelTypes, WebhookTypes} from '@fluxer/constants/src/ChannelConstants';
|
||||
import type {WorkerTaskHandler} from '@pkgs/worker/src/contracts/WorkerTask';
|
||||
import {afterAll, afterEach, beforeAll, beforeEach, describe, expect, test, vi} from 'vitest';
|
||||
import {ZodError} from 'zod';
|
||||
|
||||
const pageSizes = vi.hoisted(() => ({chunk: null as number | null, page: null as number | null}));
|
||||
|
||||
@@ -49,152 +37,6 @@ vi.mock('@fluxer/constants/src/AnnouncementConstants', async (importOriginal) =>
|
||||
};
|
||||
});
|
||||
|
||||
const CROSSPOST_TASKS = Object.values(CrosspostTaskNames);
|
||||
|
||||
describe('crosspost worker lane', () => {
|
||||
test('every crosspost task runs in the crosspost lane', () => {
|
||||
for (const task of CROSSPOST_TASKS) {
|
||||
expect(findLaneForTask(task)).toBe('crosspost');
|
||||
expect(workerTasks).toHaveProperty(task);
|
||||
}
|
||||
const lane = WORKER_LANES.find((entry) => entry.name === 'crosspost');
|
||||
expect(lane).toMatchObject({
|
||||
consumerName: 'workers_crosspost',
|
||||
concurrency: 8,
|
||||
maxAckPending: 64,
|
||||
ackWaitMs: 60000,
|
||||
maxDeliver: 25,
|
||||
});
|
||||
expect([...lane!.taskTypes].sort()).toEqual([...CROSSPOST_TASKS].sort());
|
||||
expect(() => validateLaneCompleteness(workerTasks)).not.toThrow();
|
||||
});
|
||||
|
||||
test('the lane resolves on its own and takes a concurrency override', () => {
|
||||
const [lane] = resolveWorkerLanes({
|
||||
mode: 'single_lane',
|
||||
laneName: 'crosspost',
|
||||
laneConcurrencyOverrides: {crosspost: 3},
|
||||
});
|
||||
expect(lane!.name).toBe('crosspost');
|
||||
expect(lane!.concurrency).toBe(3);
|
||||
const [single] = resolveWorkerLanes({
|
||||
mode: 'single_task',
|
||||
taskName: 'crosspostMessageChunk',
|
||||
laneConcurrencyOverrides: {},
|
||||
});
|
||||
expect(single!.name).toBe('crosspost');
|
||||
expect(single!.taskTypes).toEqual(['crosspostMessageChunk']);
|
||||
});
|
||||
});
|
||||
|
||||
describe('crosspost task payloads', () => {
|
||||
const cases: Array<[string, WorkerTaskHandler, Record<string, unknown>]> = [
|
||||
['crosspostMessage', crosspostMessage, {channelId: 1}],
|
||||
['crosspostMessageChunk', crosspostMessageChunk, {channelId: '1', messageId: '2', webhookIds: [], attempt: 0}],
|
||||
['crosspostMessageChunk', crosspostMessageChunk, {channelId: '1', messageId: '2', webhookIds: ['3'], attempt: -1}],
|
||||
['syncCrosspostedMessage', syncCrosspostedMessage, {channelId: '1', messageId: '2', mode: 'rewrite'}],
|
||||
['syncCrosspostCopies', syncCrosspostCopies, {channelId: '1', messageId: '2', mode: 'update', webhookIds: []}],
|
||||
['removeChannelFollowers', removeChannelFollowers, {sourceChannelId: '1', reason: 'archived'}],
|
||||
['removeChannelFollowers', removeChannelFollowers, {sourceChannelId: '1', reason: 'deleted', copyMode: 'update'}],
|
||||
];
|
||||
|
||||
test.each(cases)('%s rejects a malformed payload', async (_name, handler, payload) => {
|
||||
const worker = new SyncTaskWorkerService({}, {deferred: true});
|
||||
await expect(handler(payload, workerHelpers(worker))).rejects.toBeInstanceOf(ZodError);
|
||||
});
|
||||
});
|
||||
|
||||
describe('deferred SyncTaskWorkerService', () => {
|
||||
test('nested jobs go through the same queue and job keys dedupe', async () => {
|
||||
const seen: Array<string> = [];
|
||||
const worker = new SyncTaskWorkerService(
|
||||
{
|
||||
parent: async (payload, helpers) => {
|
||||
seen.push(`parent:${payload.n}`);
|
||||
await helpers.addJob('child', {n: payload.n}, {jobKey: 'child-key'});
|
||||
await helpers.addJob('child', {n: 99}, {jobKey: 'child-key'});
|
||||
},
|
||||
child: async (payload) => {
|
||||
seen.push(`child:${payload.n}`);
|
||||
},
|
||||
},
|
||||
{deferred: true},
|
||||
);
|
||||
await worker.addJob('parent', {n: 1}, {jobKey: 'parent-key', runAt: new Date(Date.now() + 60_000)});
|
||||
await worker.addJob('parent', {n: 2}, {jobKey: 'parent-key'});
|
||||
expect(seen).toEqual([]);
|
||||
expect(worker.pending).toHaveLength(1);
|
||||
const result = await worker.drain();
|
||||
expect(result).toEqual({ran: 2, failed: 0, remaining: 0});
|
||||
expect(seen).toEqual(['parent:1', 'child:1']);
|
||||
expect(worker.recorded.map((job) => job.jobKey)).toEqual(['parent-key', 'child-key']);
|
||||
expect(worker.recorded[0]!.runAt).toBeInstanceOf(Date);
|
||||
expect(worker.deduped.map((job) => job.payload)).toEqual([{n: 2}, {n: 99}]);
|
||||
await worker.addJob('parent', {n: 3}, {jobKey: 'parent-key'});
|
||||
await worker.drain();
|
||||
expect(seen.at(-1)).toBe('child:3');
|
||||
});
|
||||
|
||||
test('a failing job retries and then lands in dead letters', async () => {
|
||||
let flakyCalls = 0;
|
||||
let onceCalls = 0;
|
||||
const worker = new SyncTaskWorkerService(
|
||||
{
|
||||
flaky: async () => {
|
||||
flakyCalls++;
|
||||
throw new Error('always fails');
|
||||
},
|
||||
once: async () => {
|
||||
onceCalls++;
|
||||
if (onceCalls === 1) throw new Error('fails once');
|
||||
},
|
||||
},
|
||||
{deferred: true},
|
||||
);
|
||||
await worker.addJob('flaky', {});
|
||||
await worker.addJob('once', {});
|
||||
const result = await worker.drain({maxAttempts: 3});
|
||||
expect(result.failed).toBe(4);
|
||||
expect(flakyCalls).toBe(3);
|
||||
expect(onceCalls).toBe(2);
|
||||
expect(worker.deadLetters).toHaveLength(1);
|
||||
expect(worker.deadLetters[0]!.taskType).toBe('flaky');
|
||||
expect(worker.deadLetters[0]!.attempts).toBe(3);
|
||||
});
|
||||
|
||||
test('drain stops after maxJobs and resumes later', async () => {
|
||||
const ran: Array<number> = [];
|
||||
const worker = new SyncTaskWorkerService(
|
||||
{step: async (payload) => void ran.push(payload.n as number)},
|
||||
{deferred: true},
|
||||
);
|
||||
for (const n of [1, 2, 3]) {
|
||||
await worker.addJob('step', {n});
|
||||
}
|
||||
expect(await worker.drain({maxJobs: 2})).toEqual({ran: 2, failed: 0, remaining: 1});
|
||||
expect(ran).toEqual([1, 2]);
|
||||
await worker.drain();
|
||||
expect(ran).toEqual([1, 2, 3]);
|
||||
});
|
||||
|
||||
test('the default mode still runs inline and drops nested jobs', async () => {
|
||||
const seen: Array<string> = [];
|
||||
const worker = new SyncTaskWorkerService({
|
||||
parent: async (_payload, helpers) => {
|
||||
seen.push('parent');
|
||||
expect(await helpers.addJob('child', {})).toBe(0n);
|
||||
},
|
||||
child: async () => {
|
||||
seen.push('child');
|
||||
},
|
||||
});
|
||||
await worker.addJob('parent', {}, {jobKey: 'k'});
|
||||
await worker.addJob('parent', {}, {jobKey: 'k'});
|
||||
expect(seen).toEqual(['parent', 'parent']);
|
||||
expect(worker.recorded).toHaveLength(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe('crosspost tasks against storage', () => {
|
||||
let harness: ApiTestHarness;
|
||||
let world: FanoutWorld;
|
||||
|
||||
@@ -26,10 +26,6 @@ class RecordingWorkerService implements IWorkerService<WorkerTaskName> {
|
||||
async cancelJob(): Promise<boolean> {
|
||||
return false;
|
||||
}
|
||||
|
||||
async retryDeadLetterJob(): Promise<boolean> {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
class OverflowingWorkerService implements IWorkerService<WorkerTaskName> {
|
||||
@@ -44,10 +40,6 @@ class OverflowingWorkerService implements IWorkerService<WorkerTaskName> {
|
||||
async cancelJob(): Promise<boolean> {
|
||||
return false;
|
||||
}
|
||||
|
||||
async retryDeadLetterJob(): Promise<boolean> {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
function makeMentionMessage(): Message {
|
||||
|
||||
@@ -1,66 +1,9 @@
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
import {
|
||||
resolveCronSchedulerEnabled,
|
||||
resolveWorkerLanes,
|
||||
validateLaneCompleteness,
|
||||
WORKER_LANES,
|
||||
} from '@app/api/worker/WorkerLaneConfig';
|
||||
import {resolveWorkerLanes, validateLaneCompleteness, WORKER_LANES} from '@app/api/worker/WorkerLaneConfig';
|
||||
import {describe, expect, it} from 'vitest';
|
||||
|
||||
describe('WorkerLaneConfig', () => {
|
||||
it('returns all lanes in all_lanes mode', () => {
|
||||
const lanes = resolveWorkerLanes({
|
||||
mode: 'all_lanes',
|
||||
laneConcurrencyOverrides: {},
|
||||
});
|
||||
expect(lanes.map((lane) => lane.name)).toEqual(WORKER_LANES.map((lane) => lane.name));
|
||||
});
|
||||
it('returns a single lane in single_lane mode', () => {
|
||||
const lanes = resolveWorkerLanes({
|
||||
mode: 'single_lane',
|
||||
laneName: 'batch',
|
||||
laneConcurrencyOverrides: {},
|
||||
});
|
||||
expect(lanes).toHaveLength(1);
|
||||
expect(lanes[0]!.name).toBe('batch');
|
||||
});
|
||||
it('resolves Stripe webhook processing in lifecycle lane when running single_task mode', () => {
|
||||
const lanes = resolveWorkerLanes({
|
||||
mode: 'single_task',
|
||||
taskName: 'processStripeWebhook',
|
||||
laneConcurrencyOverrides: {},
|
||||
});
|
||||
expect(lanes).toHaveLength(1);
|
||||
expect(lanes[0]!.name).toBe('lifecycle');
|
||||
expect(lanes[0]!.taskTypes).toEqual(['processStripeWebhook']);
|
||||
});
|
||||
it('keeps mention processing in realtime and routes embed extraction to the dedicated unfurl lane', () => {
|
||||
const mentionLane = resolveWorkerLanes({
|
||||
mode: 'single_task',
|
||||
taskName: 'handleMentions',
|
||||
laneConcurrencyOverrides: {},
|
||||
});
|
||||
const mentionChunkLane = resolveWorkerLanes({
|
||||
mode: 'single_task',
|
||||
taskName: 'handleMentionChunk',
|
||||
laneConcurrencyOverrides: {},
|
||||
});
|
||||
const embedLane = resolveWorkerLanes({
|
||||
mode: 'single_task',
|
||||
taskName: 'extractEmbeds',
|
||||
laneConcurrencyOverrides: {},
|
||||
});
|
||||
expect(mentionLane).toHaveLength(1);
|
||||
expect(mentionLane[0]!.name).toBe('realtime');
|
||||
expect(mentionLane[0]!.taskTypes).toEqual(['handleMentions']);
|
||||
expect(mentionChunkLane).toHaveLength(1);
|
||||
expect(mentionChunkLane[0]!.name).toBe('realtime');
|
||||
expect(mentionChunkLane[0]!.taskTypes).toEqual(['handleMentionChunk']);
|
||||
expect(embedLane).toHaveLength(1);
|
||||
expect(embedLane[0]!.name).toBe('unfurl');
|
||||
expect(embedLane[0]!.taskTypes).toEqual(['extractEmbeds']);
|
||||
});
|
||||
it('keeps each retired subject on the lane that used to run it', () => {
|
||||
const lanes = resolveWorkerLanes({
|
||||
mode: 'all_lanes',
|
||||
@@ -93,45 +36,4 @@ describe('WorkerLaneConfig', () => {
|
||||
/Retired tasks registered again: sendScheduledMessage/,
|
||||
);
|
||||
});
|
||||
it('throws when single_lane mode has no lane configured', () => {
|
||||
expect(() =>
|
||||
resolveWorkerLanes({
|
||||
mode: 'single_lane',
|
||||
laneConcurrencyOverrides: {},
|
||||
}),
|
||||
).toThrow(/required when worker mode is "single_lane"/);
|
||||
});
|
||||
it('throws when a lane override is invalid', () => {
|
||||
expect(() =>
|
||||
resolveWorkerLanes({
|
||||
mode: 'single_lane',
|
||||
laneName: 'realtime',
|
||||
laneConcurrencyOverrides: {
|
||||
realtime: 0,
|
||||
},
|
||||
}),
|
||||
).toThrow(/Invalid concurrency override/);
|
||||
});
|
||||
it('applies per-lane concurrency overrides', () => {
|
||||
const lanes = resolveWorkerLanes({
|
||||
mode: 'all_lanes',
|
||||
laneConcurrencyOverrides: {
|
||||
realtime: 99,
|
||||
},
|
||||
});
|
||||
const realtimeLane = lanes.find((lane) => lane.name === 'realtime');
|
||||
const lifecycleLane = lanes.find((lane) => lane.name === 'lifecycle');
|
||||
expect(realtimeLane?.concurrency).toBe(99);
|
||||
expect(lifecycleLane?.concurrency).toBe(8);
|
||||
});
|
||||
it('enables cron by default in all_lanes mode', () => {
|
||||
expect(resolveCronSchedulerEnabled('all_lanes', undefined)).toBe(true);
|
||||
});
|
||||
it('disables cron by default in single_lane mode', () => {
|
||||
expect(resolveCronSchedulerEnabled('single_lane', undefined)).toBe(false);
|
||||
});
|
||||
it('respects explicit cron scheduler config', () => {
|
||||
expect(resolveCronSchedulerEnabled('single_lane', true)).toBe(true);
|
||||
expect(resolveCronSchedulerEnabled('all_lanes', false)).toBe(false);
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user