Add Remediate workflow type to Ivanti Queue with remediation notes
- Add 'Remediate' as a valid workflow type (vendor-required, like FP/Archer) - Create queue_remediation_notes table with FK cascade and 5000 char limit - Add POST/GET /api/ivanti/todo-queue/:id/notes endpoints - Include remediation_notes_count in queue item GET response - Add RemediationModal component for viewing/adding notes - Add notes count badge on Remediate queue items (purple #A855F7 theme) - Add delete confirmation warning when removing items with notes - Append remediation notes to Jira ticket descriptions - Add property-based tests for all correctness properties
This commit is contained in:
@@ -0,0 +1,141 @@
|
||||
/**
|
||||
* Property-Based Tests: Ivanti Queue Remediation — Vendor Validation
|
||||
*
|
||||
* Feature: ivanti-queue-remediation
|
||||
* Property 1: Remediate vendor validation
|
||||
*
|
||||
* For any non-empty string of 1–200 characters (trimmed, with at least one
|
||||
* non-whitespace character), submitting it as the vendor field with workflow_type
|
||||
* "Remediate" to the queue API SHALL be accepted; and for any empty,
|
||||
* whitespace-only, or >200 character vendor string, the request SHALL be
|
||||
* rejected with a 400 status.
|
||||
*
|
||||
* **Validates: Requirements 1.2, 1.3**
|
||||
*/
|
||||
|
||||
const fc = require('fast-check');
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Replicate the pure validation logic from ivantiTodoQueue.js
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
const VALID_WORKFLOW_TYPES = ['FP', 'Archer', 'CARD', 'GRANITE', 'DECOM', 'Remediate'];
|
||||
const INVENTORY_TYPES = ['CARD', 'GRANITE', 'DECOM'];
|
||||
|
||||
function isValidVendor(vendor) {
|
||||
if (typeof vendor !== 'string') return false;
|
||||
const trimmed = vendor.trim();
|
||||
return trimmed.length > 0 && trimmed.length <= 200;
|
||||
}
|
||||
|
||||
/**
|
||||
* Simulates the validation logic for the batch/single add endpoints.
|
||||
* Returns { accepted: boolean, status: number } mirroring the route behavior.
|
||||
*/
|
||||
function validateRemediateRequest(vendor) {
|
||||
const workflow_type = 'Remediate';
|
||||
|
||||
if (!VALID_WORKFLOW_TYPES.includes(workflow_type)) {
|
||||
return { accepted: false, status: 400 };
|
||||
}
|
||||
|
||||
// Remediate is NOT in INVENTORY_TYPES, so vendor is required
|
||||
if (!INVENTORY_TYPES.includes(workflow_type)) {
|
||||
if (!isValidVendor(vendor)) {
|
||||
return { accepted: false, status: 400 };
|
||||
}
|
||||
}
|
||||
|
||||
return { accepted: true, status: 201 };
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Arbitraries
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
// Valid vendor: 1–200 chars trimmed, at least one non-whitespace char
|
||||
const arbValidVendor = fc.string({ minLength: 1, maxLength: 200 }).filter(s => {
|
||||
const trimmed = s.trim();
|
||||
return trimmed.length > 0 && trimmed.length <= 200;
|
||||
});
|
||||
|
||||
// Invalid vendor: empty string
|
||||
const arbEmptyVendor = fc.constant('');
|
||||
|
||||
// Invalid vendor: whitespace-only strings
|
||||
const arbWhitespaceOnlyVendor = fc.array(
|
||||
fc.constantFrom(' ', '\t', '\n', '\r'),
|
||||
{ minLength: 1, maxLength: 50 }
|
||||
).map(arr => arr.join(''));
|
||||
|
||||
// Invalid vendor: strings > 200 chars when trimmed
|
||||
const arbOverlengthVendor = fc.string({ minLength: 201, maxLength: 400 }).filter(s => {
|
||||
return s.trim().length > 200;
|
||||
});
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Property 1: Remediate vendor validation
|
||||
// ---------------------------------------------------------------------------
|
||||
describe('Feature: ivanti-queue-remediation, Property 1: Remediate vendor validation', () => {
|
||||
it('accepts any non-empty vendor string of 1–200 trimmed characters with at least one non-whitespace', () => {
|
||||
fc.assert(
|
||||
fc.property(arbValidVendor, (vendor) => {
|
||||
const result = validateRemediateRequest(vendor);
|
||||
expect(result.accepted).toBe(true);
|
||||
expect(result.status).toBe(201);
|
||||
}),
|
||||
{ numRuns: 100 }
|
||||
);
|
||||
});
|
||||
|
||||
it('rejects empty string as vendor for Remediate workflow', () => {
|
||||
fc.assert(
|
||||
fc.property(arbEmptyVendor, (vendor) => {
|
||||
const result = validateRemediateRequest(vendor);
|
||||
expect(result.accepted).toBe(false);
|
||||
expect(result.status).toBe(400);
|
||||
}),
|
||||
{ numRuns: 10 }
|
||||
);
|
||||
});
|
||||
|
||||
it('rejects whitespace-only strings as vendor for Remediate workflow', () => {
|
||||
fc.assert(
|
||||
fc.property(arbWhitespaceOnlyVendor, (vendor) => {
|
||||
const result = validateRemediateRequest(vendor);
|
||||
expect(result.accepted).toBe(false);
|
||||
expect(result.status).toBe(400);
|
||||
}),
|
||||
{ numRuns: 100 }
|
||||
);
|
||||
});
|
||||
|
||||
it('rejects vendor strings exceeding 200 characters when trimmed', () => {
|
||||
fc.assert(
|
||||
fc.property(arbOverlengthVendor, (vendor) => {
|
||||
const result = validateRemediateRequest(vendor);
|
||||
expect(result.accepted).toBe(false);
|
||||
expect(result.status).toBe(400);
|
||||
}),
|
||||
{ numRuns: 100 }
|
||||
);
|
||||
});
|
||||
|
||||
it('rejects non-string vendor values (undefined, null, number)', () => {
|
||||
const arbNonString = fc.oneof(
|
||||
fc.constant(undefined),
|
||||
fc.constant(null),
|
||||
fc.integer(),
|
||||
fc.boolean()
|
||||
);
|
||||
|
||||
fc.assert(
|
||||
fc.property(arbNonString, (vendor) => {
|
||||
const result = validateRemediateRequest(vendor);
|
||||
expect(result.accepted).toBe(false);
|
||||
expect(result.status).toBe(400);
|
||||
}),
|
||||
{ numRuns: 100 }
|
||||
);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user