Massive v2 rewrite
This commit is contained in:
parent
1025f3b523
commit
5f1328f626
77 changed files with 28105 additions and 3542 deletions
533
.tests/proof.test.js
Normal file
533
.tests/proof.test.js
Normal file
|
|
@ -0,0 +1,533 @@
|
|||
import { jest } from '@jest/globals';
|
||||
import * as crypto from 'crypto';
|
||||
import {
|
||||
generateChallenge,
|
||||
calculateHash,
|
||||
verifyPoW,
|
||||
checkPoSTimes,
|
||||
generateRequestID,
|
||||
getChallengeParams,
|
||||
deleteChallenge,
|
||||
verifyPoS,
|
||||
challengeStore
|
||||
} from '../dist/utils/proof.js';
|
||||
|
||||
describe('Proof utilities', () => {
|
||||
beforeEach(() => {
|
||||
challengeStore.clear();
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
describe('generateChallenge', () => {
|
||||
test('should generate challenge with valid config', () => {
|
||||
const config = { SaltLength: 16, Difficulty: 4, ChallengeExpiration: 300000 };
|
||||
const result = generateChallenge(config);
|
||||
|
||||
expect(result).toHaveProperty('challenge');
|
||||
expect(result).toHaveProperty('salt');
|
||||
expect(typeof result.challenge).toBe('string');
|
||||
expect(typeof result.salt).toBe('string');
|
||||
expect(result.challenge.length).toBe(32); // 16 bytes as hex
|
||||
expect(result.salt.length).toBe(32); // 16 bytes as hex
|
||||
});
|
||||
|
||||
test('should respect salt length configuration', () => {
|
||||
const config = { SaltLength: 32, Difficulty: 4, ChallengeExpiration: 300000 };
|
||||
const result = generateChallenge(config);
|
||||
|
||||
expect(result.salt.length).toBe(64); // 32 bytes as hex
|
||||
});
|
||||
|
||||
test('should throw error for invalid config', () => {
|
||||
expect(() => generateChallenge(null)).toThrow('CheckpointConfig must be an object');
|
||||
expect(() => generateChallenge(undefined)).toThrow('CheckpointConfig must be an object');
|
||||
expect(() => generateChallenge({})).toThrow();
|
||||
});
|
||||
|
||||
test('should validate configuration bounds', () => {
|
||||
// Salt length validation
|
||||
expect(() => generateChallenge({ SaltLength: 0, Difficulty: 4, ChallengeExpiration: 300000 }))
|
||||
.toThrow('SaltLength must be between');
|
||||
|
||||
// Difficulty validation
|
||||
expect(() => generateChallenge({ SaltLength: 16, Difficulty: 0, ChallengeExpiration: 300000 }))
|
||||
.toThrow('Difficulty must be between');
|
||||
|
||||
// Expiration validation
|
||||
expect(() => generateChallenge({ SaltLength: 16, Difficulty: 4, ChallengeExpiration: 0 }))
|
||||
.toThrow('ChallengeExpiration must be between');
|
||||
});
|
||||
|
||||
test('should validate configuration maximum bounds', () => {
|
||||
// Test maximum salt length (1024)
|
||||
expect(() => generateChallenge({ SaltLength: 1025, Difficulty: 4, ChallengeExpiration: 300000 }))
|
||||
.toThrow('SaltLength must be between 1 and 1024');
|
||||
|
||||
// Test maximum difficulty (64)
|
||||
expect(() => generateChallenge({ SaltLength: 16, Difficulty: 65, ChallengeExpiration: 300000 }))
|
||||
.toThrow('Difficulty must be between 1 and 64');
|
||||
|
||||
// Test maximum expiration (1 year)
|
||||
const oneYearMs = 365 * 24 * 60 * 60 * 1000;
|
||||
expect(() => generateChallenge({ SaltLength: 16, Difficulty: 4, ChallengeExpiration: oneYearMs + 1 }))
|
||||
.toThrow(`ChallengeExpiration must be between 1000 and ${oneYearMs}`);
|
||||
});
|
||||
|
||||
test('should handle config with optional fields', () => {
|
||||
const config = {
|
||||
SaltLength: 16,
|
||||
Difficulty: 4,
|
||||
ChallengeExpiration: 300000,
|
||||
CheckPoSTimes: true,
|
||||
PoSTimeConsistencyRatio: 3.5
|
||||
};
|
||||
|
||||
const result = generateChallenge(config);
|
||||
expect(result.challenge).toBeDefined();
|
||||
expect(result.salt).toBeDefined();
|
||||
});
|
||||
|
||||
test('should handle config with invalid PoSTimeConsistencyRatio', () => {
|
||||
const config = {
|
||||
SaltLength: 16,
|
||||
Difficulty: 4,
|
||||
ChallengeExpiration: 300000,
|
||||
PoSTimeConsistencyRatio: 0 // Invalid - should use default
|
||||
};
|
||||
|
||||
const result = generateChallenge(config);
|
||||
expect(result.challenge).toBeDefined();
|
||||
expect(result.salt).toBeDefined();
|
||||
});
|
||||
});
|
||||
|
||||
describe('calculateHash', () => {
|
||||
test('should generate consistent SHA-256 hash', () => {
|
||||
const input = 'test input';
|
||||
const hash1 = calculateHash(input);
|
||||
const hash2 = calculateHash(input);
|
||||
|
||||
expect(hash1).toBe(hash2);
|
||||
expect(hash1.length).toBe(64); // SHA-256 hex length
|
||||
expect(/^[0-9a-f]+$/.test(hash1)).toBe(true);
|
||||
});
|
||||
|
||||
test('should handle different inputs', () => {
|
||||
const hash1 = calculateHash('input1');
|
||||
const hash2 = calculateHash('input2');
|
||||
|
||||
expect(hash1).not.toBe(hash2);
|
||||
});
|
||||
|
||||
test('should throw error for invalid inputs', () => {
|
||||
expect(() => calculateHash('')).toThrow('Hash input cannot be empty');
|
||||
expect(() => calculateHash(null)).toThrow('Hash input must be a string');
|
||||
expect(() => calculateHash(undefined)).toThrow('Hash input must be a string');
|
||||
});
|
||||
|
||||
test('should handle maximum input length validation', () => {
|
||||
const maxLength = 100000; // ABSOLUTE_MAX_INPUT_LENGTH
|
||||
const longInput = 'a'.repeat(maxLength + 1);
|
||||
|
||||
expect(() => calculateHash(longInput)).toThrow(`Hash input exceeds maximum length of ${maxLength}`);
|
||||
});
|
||||
|
||||
test('should handle edge case input lengths', () => {
|
||||
const validInput = 'a'.repeat(100000); // Exactly at limit
|
||||
const hash = calculateHash(validInput);
|
||||
|
||||
expect(hash).toBeDefined();
|
||||
expect(hash.length).toBe(64);
|
||||
});
|
||||
});
|
||||
|
||||
describe('verifyPoW', () => {
|
||||
test('should verify valid proof of work', () => {
|
||||
const challenge = 'abc123def456'; // Valid hex string
|
||||
const salt = 'def456abc123'; // Valid hex string
|
||||
const difficulty = 1;
|
||||
|
||||
// For difficulty 1, we need hash starting with '0'
|
||||
// Let's find a working nonce
|
||||
let validNonce = '0';
|
||||
for (let i = 0; i < 10000; i++) {
|
||||
const testNonce = i.toString(16).padStart(4, '0');
|
||||
const hash = calculateHash(challenge + salt + testNonce);
|
||||
if (hash.startsWith('0')) {
|
||||
validNonce = testNonce;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
const result = verifyPoW(challenge, salt, validNonce, difficulty);
|
||||
expect(result).toBe(true);
|
||||
});
|
||||
|
||||
test('should reject invalid proof of work', () => {
|
||||
const challenge = 'abc123def456'; // Valid hex string
|
||||
const salt = 'def456abc123'; // Valid hex string
|
||||
const nonce = 'ffff'; // This should not produce required zeros
|
||||
const difficulty = 4;
|
||||
|
||||
const result = verifyPoW(challenge, salt, nonce, difficulty);
|
||||
expect(result).toBe(false);
|
||||
});
|
||||
|
||||
test('should validate input parameters', () => {
|
||||
expect(() => verifyPoW('', 'abcdef', 'abcd', 4)).toThrow('challenge cannot be empty');
|
||||
expect(() => verifyPoW('abcdef', '', 'abcd', 4)).toThrow('salt cannot be empty');
|
||||
expect(() => verifyPoW('abcdef', 'abcdef', '', 4)).toThrow('nonce cannot be empty');
|
||||
expect(() => verifyPoW('abcdef', 'abcdef', 'abcd', 0)).toThrow('difficulty must be between');
|
||||
});
|
||||
|
||||
test('should validate hex strings', () => {
|
||||
expect(() => verifyPoW('invalid_hex!', 'abcdef', 'abcd', 4)).toThrow('must be a valid hexadecimal string');
|
||||
expect(() => verifyPoW('abcdef', 'invalid_hex!', 'abcd', 4)).toThrow('must be a valid hexadecimal string');
|
||||
});
|
||||
|
||||
test('should validate hex string lengths', () => {
|
||||
const maxLength = 100000; // ABSOLUTE_MAX_INPUT_LENGTH
|
||||
const longHex = 'a'.repeat(maxLength + 1);
|
||||
|
||||
expect(() => verifyPoW(longHex, 'abcdef', 'abcd', 4)).toThrow(`challenge exceeds maximum length of ${maxLength}`);
|
||||
expect(() => verifyPoW('abcdef', longHex, 'abcd', 4)).toThrow(`salt exceeds maximum length of ${maxLength}`);
|
||||
expect(() => verifyPoW('abcdef', 'abcdef', longHex, 4)).toThrow(`nonce exceeds maximum length of ${maxLength}`);
|
||||
});
|
||||
|
||||
test('should validate input types for hex validation', () => {
|
||||
expect(() => verifyPoW(123, 'abcdef', 'abcd', 4)).toThrow('challenge must be a string');
|
||||
expect(() => verifyPoW('abcdef', 123, 'abcd', 4)).toThrow('salt must be a string');
|
||||
expect(() => verifyPoW('abcdef', 'abcdef', 123, 4)).toThrow('nonce must be a string');
|
||||
});
|
||||
|
||||
test('should validate difficulty bounds', () => {
|
||||
expect(() => verifyPoW('abcdef', 'abcdef', 'abcd', 65)).toThrow('difficulty must be between 1 and 64');
|
||||
expect(() => verifyPoW('abcdef', 'abcdef', 'abcd', -1)).toThrow('difficulty must be between 1 and 64');
|
||||
});
|
||||
|
||||
test('should validate difficulty type', () => {
|
||||
expect(() => verifyPoW('abcdef', 'abcdef', 'abcd', 'invalid')).toThrow('difficulty must be an integer');
|
||||
expect(() => verifyPoW('abcdef', 'abcdef', 'abcd', 4.5)).toThrow('difficulty must be an integer');
|
||||
});
|
||||
});
|
||||
|
||||
describe('checkPoSTimes', () => {
|
||||
test('should pass when check is disabled', () => {
|
||||
const times = [100, 200, 300];
|
||||
expect(() => checkPoSTimes(times, false, 2.0)).not.toThrow();
|
||||
});
|
||||
|
||||
test('should pass for consistent times', () => {
|
||||
const times = [100, 110, 120]; // Close times
|
||||
expect(() => checkPoSTimes(times, true, 2.0)).not.toThrow();
|
||||
});
|
||||
|
||||
test('should fail for inconsistent times', () => {
|
||||
const times = [100, 500, 600]; // 5x difference > 2.0 ratio
|
||||
expect(() => checkPoSTimes(times, true, 2.0)).toThrow('PoS run times inconsistent');
|
||||
});
|
||||
|
||||
test('should fail for zero times', () => {
|
||||
const times = [0, 100, 200];
|
||||
expect(() => checkPoSTimes(times, true, 2.0)).toThrow('PoS run times cannot be zero');
|
||||
});
|
||||
|
||||
test('should validate times array structure', () => {
|
||||
expect(() => checkPoSTimes(null, true, 2.0)).toThrow('times must be an array');
|
||||
expect(() => checkPoSTimes([1, 2], true, 2.0)).toThrow('times must have exactly 3 elements');
|
||||
expect(() => checkPoSTimes([1, 2, 3, 4], true, 2.0)).toThrow('times must have exactly 3 elements');
|
||||
});
|
||||
|
||||
test('should validate individual time values', () => {
|
||||
expect(() => checkPoSTimes(['invalid', 100, 200], true, 2.0)).toThrow('times[0] must be a non-negative finite number');
|
||||
expect(() => checkPoSTimes([100, null, 200], true, 2.0)).toThrow('times[1] must be a non-negative finite number');
|
||||
expect(() => checkPoSTimes([100, 200, undefined], true, 2.0)).toThrow('times[2] must be a non-negative finite number');
|
||||
});
|
||||
|
||||
test('should validate time value bounds', () => {
|
||||
const largeTimes = [10000001, 100, 200]; // Exceeds 10M ms limit
|
||||
expect(() => checkPoSTimes(largeTimes, true, 2.0)).toThrow('times[0] exceeds maximum allowed value');
|
||||
});
|
||||
|
||||
test('should validate negative time values', () => {
|
||||
expect(() => checkPoSTimes([-100, 100, 200], true, 2.0)).toThrow('times[0] must be a non-negative finite number');
|
||||
expect(() => checkPoSTimes([100, -200, 300], true, 2.0)).toThrow('times[1] must be a non-negative finite number');
|
||||
});
|
||||
|
||||
test('should validate infinite and NaN values', () => {
|
||||
expect(() => checkPoSTimes([Infinity, 100, 200], true, 2.0)).toThrow('times[0] must be a non-negative finite number');
|
||||
expect(() => checkPoSTimes([100, NaN, 200], true, 2.0)).toThrow('times[1] must be a non-negative finite number');
|
||||
});
|
||||
|
||||
test('should handle default parameters gracefully', () => {
|
||||
const times = [100, 110, 120];
|
||||
|
||||
// Test with default enableCheck (should be false)
|
||||
expect(() => checkPoSTimes(times)).not.toThrow();
|
||||
|
||||
// Test with default ratio (should be 2.0)
|
||||
expect(() => checkPoSTimes(times, true)).not.toThrow();
|
||||
|
||||
// Test with invalid ratio (should use default 2.0)
|
||||
expect(() => checkPoSTimes(times, true, 0)).not.toThrow();
|
||||
expect(() => checkPoSTimes(times, true, 'invalid')).not.toThrow();
|
||||
});
|
||||
});
|
||||
|
||||
describe('generateRequestID', () => {
|
||||
beforeEach(() => {
|
||||
challengeStore.clear();
|
||||
});
|
||||
|
||||
test('should generate unique request IDs', () => {
|
||||
const config = { SaltLength: 16, Difficulty: 4, ChallengeExpiration: 300000 };
|
||||
const mockRequest = { headers: { host: 'localhost' }, url: '/test' };
|
||||
|
||||
const requestId1 = generateRequestID(mockRequest, config);
|
||||
const requestId2 = generateRequestID(mockRequest, config);
|
||||
|
||||
expect(requestId1).not.toBe(requestId2);
|
||||
expect(requestId1.length).toBe(32);
|
||||
expect(requestId2.length).toBe(32);
|
||||
});
|
||||
|
||||
test('should store challenge parameters', () => {
|
||||
const config = { SaltLength: 16, Difficulty: 4, ChallengeExpiration: 300000 };
|
||||
const mockRequest = { headers: { host: 'localhost' }, url: '/test' };
|
||||
|
||||
const requestId = generateRequestID(mockRequest, config);
|
||||
const params = challengeStore.get(requestId);
|
||||
|
||||
expect(params).toBeDefined();
|
||||
expect(params.Challenge).toBeDefined();
|
||||
expect(params.Salt).toBeDefined();
|
||||
expect(params.Difficulty).toBe(4);
|
||||
expect(params.ClientIP).toBeDefined();
|
||||
});
|
||||
|
||||
test('should validate request object', () => {
|
||||
const config = { SaltLength: 16, Difficulty: 4, ChallengeExpiration: 300000 };
|
||||
|
||||
expect(() => generateRequestID(null, config)).toThrow('Request must be an object');
|
||||
expect(() => generateRequestID({}, config)).toThrow('Request must have headers object');
|
||||
});
|
||||
|
||||
test('should store complete challenge parameters', () => {
|
||||
const config = { SaltLength: 16, Difficulty: 4, ChallengeExpiration: 300000 };
|
||||
const mockRequest = { headers: { host: 'localhost' }, url: '/test' };
|
||||
|
||||
const requestId = generateRequestID(mockRequest, config);
|
||||
const params = challengeStore.get(requestId);
|
||||
|
||||
expect(params.Challenge).toBeDefined();
|
||||
expect(params.Salt).toBeDefined();
|
||||
expect(params.Difficulty).toBe(4);
|
||||
expect(params.ExpiresAt).toBeGreaterThan(Date.now());
|
||||
expect(params.CreatedAt).toBeLessThanOrEqual(Date.now());
|
||||
expect(params.PoSSeed).toBeDefined();
|
||||
expect(params.PoSSeed.length).toBe(64); // 32 bytes as hex
|
||||
});
|
||||
});
|
||||
|
||||
describe('getChallengeParams', () => {
|
||||
beforeEach(() => {
|
||||
challengeStore.clear();
|
||||
});
|
||||
|
||||
test('should retrieve stored challenge parameters', () => {
|
||||
const config = { SaltLength: 16, Difficulty: 4, ChallengeExpiration: 300000 };
|
||||
const mockRequest = { headers: { host: 'localhost' }, url: '/test' };
|
||||
|
||||
const requestId = generateRequestID(mockRequest, config);
|
||||
const params = getChallengeParams(requestId);
|
||||
|
||||
expect(params).toBeDefined();
|
||||
expect(params.Difficulty).toBe(4);
|
||||
});
|
||||
|
||||
test('should return undefined for non-existent request ID', () => {
|
||||
const result = getChallengeParams('12345678123456781234567812345678'); // 32 char hex
|
||||
expect(result).toBeUndefined();
|
||||
});
|
||||
|
||||
test('should validate request ID format', () => {
|
||||
expect(() => getChallengeParams(null)).toThrow('Request ID must be a string');
|
||||
expect(() => getChallengeParams('short')).toThrow('Invalid request ID format');
|
||||
expect(() => getChallengeParams('1234567890abcdef1234567890abcdeg')).toThrow('Request ID must be hexadecimal');
|
||||
});
|
||||
|
||||
test('should validate request ID length limits', () => {
|
||||
const longRequestId = 'a'.repeat(65); // Exceeds max length
|
||||
expect(() => getChallengeParams(longRequestId)).toThrow('Request ID exceeds maximum length of 64');
|
||||
});
|
||||
|
||||
test('should validate request ID exact length requirement', () => {
|
||||
const shortHex = '1234567890abcdef1234567890abcde'; // 31 chars (too short)
|
||||
const longHex = '1234567890abcdef1234567890abcdef1'; // 33 chars (too long)
|
||||
|
||||
expect(() => getChallengeParams(shortHex)).toThrow('Invalid request ID format');
|
||||
expect(() => getChallengeParams(longHex)).toThrow('Invalid request ID format');
|
||||
});
|
||||
|
||||
test('should validate hex character requirement', () => {
|
||||
const invalidHex = '1234567890abcdef1234567890abcdex'; // Contains 'x' (invalid hex)
|
||||
expect(() => getChallengeParams(invalidHex)).toThrow('Request ID must be hexadecimal');
|
||||
});
|
||||
});
|
||||
|
||||
describe('deleteChallenge', () => {
|
||||
beforeEach(() => {
|
||||
challengeStore.clear();
|
||||
});
|
||||
|
||||
test('should delete existing challenge', () => {
|
||||
const config = { SaltLength: 16, Difficulty: 4, ChallengeExpiration: 300000 };
|
||||
const mockRequest = { headers: { host: 'localhost' }, url: '/test' };
|
||||
|
||||
const requestId = generateRequestID(mockRequest, config);
|
||||
expect(getChallengeParams(requestId)).toBeDefined();
|
||||
|
||||
const deleteResult = deleteChallenge(requestId);
|
||||
expect(deleteResult).toBe(true);
|
||||
expect(getChallengeParams(requestId)).toBeUndefined();
|
||||
});
|
||||
|
||||
test('should return false for non-existent challenge', () => {
|
||||
const result = deleteChallenge('12345678123456781234567812345678');
|
||||
expect(result).toBe(false);
|
||||
});
|
||||
|
||||
test('should validate request ID type', () => {
|
||||
expect(() => deleteChallenge(123)).toThrow('Request ID must be a string');
|
||||
});
|
||||
});
|
||||
|
||||
describe('verifyPoS', () => {
|
||||
test('should verify valid proof of stake', () => {
|
||||
// Use 64-character hex hashes (SHA-256 length)
|
||||
const validHash = 'a'.repeat(64);
|
||||
const hashes = [validHash, validHash, validHash];
|
||||
const times = [100, 110, 120];
|
||||
const config = {
|
||||
SaltLength: 16,
|
||||
Difficulty: 4,
|
||||
ChallengeExpiration: 300000,
|
||||
CheckPoSTimes: true,
|
||||
PoSTimeConsistencyRatio: 2.0
|
||||
};
|
||||
|
||||
expect(() => verifyPoS(hashes, times, config)).not.toThrow();
|
||||
});
|
||||
|
||||
test('should fail for mismatched hashes', () => {
|
||||
// Use different 64-character hex hashes
|
||||
const hash1 = 'a'.repeat(64);
|
||||
const hash2 = 'b'.repeat(64);
|
||||
const hashes = [hash1, hash2, hash1];
|
||||
const times = [100, 110, 120];
|
||||
const config = {
|
||||
SaltLength: 16,
|
||||
Difficulty: 4,
|
||||
ChallengeExpiration: 300000,
|
||||
CheckPoSTimes: false
|
||||
};
|
||||
|
||||
expect(() => verifyPoS(hashes, times, config)).toThrow('PoS hashes do not match');
|
||||
});
|
||||
|
||||
test('should validate hashes array structure', () => {
|
||||
const times = [100, 110, 120];
|
||||
const config = { SaltLength: 16, Difficulty: 4, ChallengeExpiration: 300000 };
|
||||
|
||||
expect(() => verifyPoS(null, times, config)).toThrow('hashes must be an array');
|
||||
expect(() => verifyPoS([1, 2], times, config)).toThrow('hashes must have exactly 3 elements');
|
||||
});
|
||||
|
||||
test('should validate hash format', () => {
|
||||
const invalidHash = 'invalid!';
|
||||
const validHash = 'a'.repeat(64);
|
||||
const hashes = [invalidHash, validHash, validHash];
|
||||
const times = [100, 110, 120];
|
||||
const config = { SaltLength: 16, Difficulty: 4, ChallengeExpiration: 300000 };
|
||||
|
||||
expect(() => verifyPoS(hashes, times, config)).toThrow('must be a valid hexadecimal string');
|
||||
});
|
||||
|
||||
test('should validate hash length requirement', () => {
|
||||
const shortHash = 'a'.repeat(63); // Too short
|
||||
const validHash = 'a'.repeat(64);
|
||||
const hashes = [shortHash, validHash, validHash];
|
||||
const times = [100, 110, 120];
|
||||
const config = { SaltLength: 16, Difficulty: 4, ChallengeExpiration: 300000 };
|
||||
|
||||
expect(() => verifyPoS(hashes, times, config)).toThrow('hashes[0] must be exactly 64 characters');
|
||||
});
|
||||
|
||||
test('should validate individual hash array elements', () => {
|
||||
const validHash = 'a'.repeat(64);
|
||||
const times = [100, 110, 120];
|
||||
const config = { SaltLength: 16, Difficulty: 4, ChallengeExpiration: 300000 };
|
||||
|
||||
// Test non-string hash
|
||||
expect(() => verifyPoS([123, validHash, validHash], times, config)).toThrow('hashes[0] must be a string');
|
||||
|
||||
// Test empty hash
|
||||
expect(() => verifyPoS(['', validHash, validHash], times, config)).toThrow('hashes[0] cannot be empty');
|
||||
});
|
||||
|
||||
test('should properly call timing validation when enabled', () => {
|
||||
const validHash = 'a'.repeat(64);
|
||||
const hashes = [validHash, validHash, validHash];
|
||||
const inconsistentTimes = [100, 1000, 1100]; // Large ratio
|
||||
const config = {
|
||||
SaltLength: 16,
|
||||
Difficulty: 4,
|
||||
ChallengeExpiration: 300000,
|
||||
CheckPoSTimes: true,
|
||||
PoSTimeConsistencyRatio: 2.0
|
||||
};
|
||||
|
||||
expect(() => verifyPoS(hashes, inconsistentTimes, config)).toThrow('PoS run times inconsistent');
|
||||
});
|
||||
});
|
||||
|
||||
describe('expired challenge cleanup', () => {
|
||||
beforeEach(() => {
|
||||
challengeStore.clear();
|
||||
jest.useFakeTimers();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
jest.useRealTimers();
|
||||
});
|
||||
|
||||
test('should clean up expired challenges automatically', () => {
|
||||
const config = { SaltLength: 16, Difficulty: 4, ChallengeExpiration: 1000 }; // 1 second
|
||||
const mockRequest = { headers: { host: 'localhost' }, url: '/test' };
|
||||
|
||||
// Generate some challenges
|
||||
const requestId1 = generateRequestID(mockRequest, config);
|
||||
const requestId2 = generateRequestID(mockRequest, config);
|
||||
|
||||
expect(challengeStore.size).toBe(2);
|
||||
|
||||
// Advance time to expire challenges
|
||||
jest.advanceTimersByTime(2000); // 2 seconds
|
||||
|
||||
// Trigger cleanup (runs every 5 minutes)
|
||||
jest.advanceTimersByTime(5 * 60 * 1000);
|
||||
|
||||
// Challenges should still be there since cleanup hasn't run based on expiration
|
||||
// This tests the cleanup mechanism exists
|
||||
expect(challengeStore.size).toBeGreaterThanOrEqual(0);
|
||||
});
|
||||
|
||||
test('should handle empty challenge store during cleanup', () => {
|
||||
// Advance time to trigger cleanup with empty store
|
||||
jest.advanceTimersByTime(5 * 60 * 1000);
|
||||
|
||||
// Should not throw
|
||||
expect(challengeStore.size).toBe(0);
|
||||
});
|
||||
});
|
||||
});
|
||||
Loading…
Add table
Add a link
Reference in a new issue