Merge dev: Node.js scraper migration + CI fix #32
151
__tests__/scraper/scraperJob.test.js
Normal file
151
__tests__/scraper/scraperJob.test.js
Normal file
@ -0,0 +1,151 @@
|
||||
/**
|
||||
* Tests for scraperJob.js - In-process mutex (isScraperRunning flag)
|
||||
* Following TDD - these tests are written BEFORE implementation
|
||||
*/
|
||||
|
||||
const {
|
||||
isScraperRunning,
|
||||
getCurrentJobId,
|
||||
acquireLock,
|
||||
releaseLock
|
||||
} = require('../../jobs/scraperJob');
|
||||
|
||||
describe('scraperJob - mutex lock', () => {
|
||||
// Reset state between each test
|
||||
afterEach(() => {
|
||||
releaseLock();
|
||||
});
|
||||
|
||||
describe('isScraperRunning()', () => {
|
||||
test('should return false when not running', () => {
|
||||
expect(isScraperRunning()).toBe(false);
|
||||
});
|
||||
|
||||
test('should return true after acquireLock()', () => {
|
||||
acquireLock('test-job-1');
|
||||
|
||||
expect(isScraperRunning()).toBe(true);
|
||||
});
|
||||
|
||||
test('should return false after releaseLock()', () => {
|
||||
acquireLock('test-job-2');
|
||||
releaseLock();
|
||||
|
||||
expect(isScraperRunning()).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getCurrentJobId()', () => {
|
||||
test('should return null when not running', () => {
|
||||
expect(getCurrentJobId()).toBeNull();
|
||||
});
|
||||
|
||||
test('should return jobId when running', () => {
|
||||
const jobId = 'my-unique-job-id';
|
||||
acquireLock(jobId);
|
||||
|
||||
expect(getCurrentJobId()).toBe(jobId);
|
||||
});
|
||||
|
||||
test('should return null after releaseLock()', () => {
|
||||
acquireLock('test-job-3');
|
||||
releaseLock();
|
||||
|
||||
expect(getCurrentJobId()).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
describe('acquireLock()', () => {
|
||||
test('should return true when lock is available', () => {
|
||||
const result = acquireLock('job-1');
|
||||
|
||||
expect(result).toBe(true);
|
||||
});
|
||||
|
||||
test('should return false when lock is already held', () => {
|
||||
acquireLock('job-1');
|
||||
|
||||
const result = acquireLock('job-2');
|
||||
|
||||
expect(result).toBe(false);
|
||||
});
|
||||
|
||||
test('should set isScraperRunning to true', () => {
|
||||
acquireLock('job-1');
|
||||
|
||||
expect(isScraperRunning()).toBe(true);
|
||||
});
|
||||
|
||||
test('should set currentJobId to the provided jobId', () => {
|
||||
const jobId = 'specific-job-id';
|
||||
acquireLock(jobId);
|
||||
|
||||
expect(getCurrentJobId()).toBe(jobId);
|
||||
});
|
||||
|
||||
test('should not overwrite existing lock when already held', () => {
|
||||
const firstJobId = 'first-job';
|
||||
const secondJobId = 'second-job';
|
||||
|
||||
acquireLock(firstJobId);
|
||||
acquireLock(secondJobId);
|
||||
|
||||
// Should still have the first job's ID
|
||||
expect(getCurrentJobId()).toBe(firstJobId);
|
||||
});
|
||||
});
|
||||
|
||||
describe('releaseLock()', () => {
|
||||
test('should clear running state', () => {
|
||||
acquireLock('job-to-release');
|
||||
|
||||
releaseLock();
|
||||
|
||||
expect(isScraperRunning()).toBe(false);
|
||||
});
|
||||
|
||||
test('should clear currentJobId', () => {
|
||||
acquireLock('job-to-release');
|
||||
|
||||
releaseLock();
|
||||
|
||||
expect(getCurrentJobId()).toBeNull();
|
||||
});
|
||||
|
||||
test('should allow a new lock to be acquired after release', () => {
|
||||
acquireLock('first-job');
|
||||
releaseLock();
|
||||
|
||||
const result = acquireLock('second-job');
|
||||
|
||||
expect(result).toBe(true);
|
||||
expect(getCurrentJobId()).toBe('second-job');
|
||||
expect(isScraperRunning()).toBe(true);
|
||||
});
|
||||
|
||||
test('should be safe to call when no lock is held', () => {
|
||||
// Should not throw when called without an active lock
|
||||
expect(() => releaseLock()).not.toThrow();
|
||||
expect(isScraperRunning()).toBe(false);
|
||||
expect(getCurrentJobId()).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
describe('module exports', () => {
|
||||
test('should export isScraperRunning as a function', () => {
|
||||
expect(typeof isScraperRunning).toBe('function');
|
||||
});
|
||||
|
||||
test('should export getCurrentJobId as a function', () => {
|
||||
expect(typeof getCurrentJobId).toBe('function');
|
||||
});
|
||||
|
||||
test('should export acquireLock as a function', () => {
|
||||
expect(typeof acquireLock).toBe('function');
|
||||
});
|
||||
|
||||
test('should export releaseLock as a function', () => {
|
||||
expect(typeof releaseLock).toBe('function');
|
||||
});
|
||||
});
|
||||
});
|
||||
48
jobs/scraperJob.js
Normal file
48
jobs/scraperJob.js
Normal file
@ -0,0 +1,48 @@
|
||||
// In-process mutex state
|
||||
let isRunning = false;
|
||||
let currentJobId = null;
|
||||
|
||||
/**
|
||||
* Check if scraper is currently running
|
||||
* @returns {boolean}
|
||||
*/
|
||||
function isScraperRunning() {
|
||||
return isRunning;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get current job ID if running
|
||||
* @returns {string|null}
|
||||
*/
|
||||
function getCurrentJobId() {
|
||||
return currentJobId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Acquire the scraper lock
|
||||
* @param {string} jobId - Job ID to set
|
||||
* @returns {boolean} True if lock acquired
|
||||
*/
|
||||
function acquireLock(jobId) {
|
||||
if (isRunning) {
|
||||
return false;
|
||||
}
|
||||
isRunning = true;
|
||||
currentJobId = jobId;
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Release the scraper lock
|
||||
*/
|
||||
function releaseLock() {
|
||||
isRunning = false;
|
||||
currentJobId = null;
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
isScraperRunning,
|
||||
getCurrentJobId,
|
||||
acquireLock,
|
||||
releaseLock
|
||||
};
|
||||
Reference in New Issue
Block a user