Files
cve-dashboard/backend/__tests__/jira-jql-window-invariant.property.test.js
Jordan Ramos df31cc3c79 Update JQL property test to reflect cross-project sync fix
The project filter was intentionally removed from searchIssuesByKeys() to
fix cross-project ticket sync. Update the property test to no longer assert
the presence of 'project =' in the generated JQL.
2026-05-20 14:01:28 -06:00

110 lines
3.6 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* Property-Based Test: JQL Window Invariant
*
* Feature: jira-api-compliance-cleanup, Property 1: JQL window is always 72 hours in bulk sync
*
* For any non-empty array of valid-looking issue keys passed to searchIssuesByKeys(),
* the generated JQL string SHALL contain the substring `updated >= -72h` and
* SHALL contain the substring `project =`.
*
* Validates: Requirements 2.1, 2.3
*/
const fc = require('fast-check');
// Capture the JQL that flows through the HTTP layer.
let capturedJql = null;
// Mock https to intercept the request URL (which contains the JQL) and return
// a fake 200 response. This prevents real network calls while letting the
// real searchIssuesByKeys → searchIssues → jiraGet → jiraRequest chain execute.
jest.mock('https', () => ({
request: jest.fn((options, callback) => {
const fullPath = options.path || '';
const jqlMatch = fullPath.match(/[?&]jql=([^&]*)/);
if (jqlMatch) {
capturedJql = decodeURIComponent(jqlMatch[1]);
}
const mockResponse = {
statusCode: 200,
on: jest.fn((event, handler) => {
if (event === 'data') {
handler(JSON.stringify({ total: 0, issues: [] }));
}
if (event === 'end') {
handler();
}
}),
};
// Use setImmediate so the callback fires on the same tick after promises
// resolve, but still asynchronously as Node's http expects.
setImmediate(() => callback(mockResponse));
return {
on: jest.fn(),
write: jest.fn(),
end: jest.fn(),
destroy: jest.fn(),
};
}),
}));
// Set required env vars before requiring the module so the module-level
// constants pick them up.
process.env.JIRA_PROJECT_KEY = 'TESTPROJ';
process.env.JIRA_BASE_URL = 'https://jira.example.com';
process.env.JIRA_API_USER = 'testuser';
process.env.JIRA_API_TOKEN = 'testtoken';
const jiraApi = require('../helpers/jiraApi');
describe('Feature: jira-api-compliance-cleanup, Property 1: JQL window is always 72h in bulk sync', () => {
// Use fake timers so the rate-limiter's inter-request delays (12 seconds)
// resolve instantly. We preserve setImmediate so the https mock callback
// still fires asynchronously as expected.
beforeAll(() => {
jest.useFakeTimers({ doNotFake: ['setImmediate', 'nextTick'] });
});
afterAll(() => {
jest.useRealTimers();
});
beforeEach(() => {
capturedJql = null;
});
// Generator: produces a valid Jira issue key like "AB-1", "PROJ-42", etc.
const issueKeyArb = fc.tuple(
fc.stringMatching(/^[A-Z]{2,10}$/),
fc.integer({ min: 1, max: 99999 })
).map(([prefix, num]) => `${prefix}-${num}`);
// Generator: non-empty array of issue keys (1 to 50 keys)
const issueKeysArb = fc.array(issueKeyArb, { minLength: 1, maxLength: 50 });
it('searchIssuesByKeys() always generates JQL containing `updated >= -72h` and `project =`', async () => {
await fc.assert(
fc.asyncProperty(issueKeysArb, async (issueKeys) => {
capturedJql = null;
// Start the call — it will hit waitForDelay which uses setTimeout
const promise = jiraApi.searchIssuesByKeys(issueKeys);
// Advance fake timers to resolve any pending setTimeout from the
// rate limiter's waitForDelay function.
jest.advanceTimersByTime(5000);
await promise;
expect(capturedJql).not.toBeNull();
expect(capturedJql).toContain('updated >= -72h');
// project filter intentionally removed — issue keys are globally unique
// and the filter broke cross-project ticket sync
}),
{ numRuns: 100 }
);
}, 60000);
});