2
0
mirror of https://github.com/iconify/iconify.git synced 2024-12-13 14:13:06 +00:00

Merge branch 'dev/redundancy'

This commit is contained in:
Vjacheslav Trushkin 2022-01-27 09:36:10 +02:00
commit ded43ed8fc
19 changed files with 2278 additions and 2132 deletions

View File

@ -15,6 +15,6 @@ module.exports = {
rules: { rules: {
'no-mixed-spaces-and-tabs': ['off'], 'no-mixed-spaces-and-tabs': ['off'],
'no-unused-vars': ['off'], 'no-unused-vars': ['off'],
'@typescript-eslint/no-unused-vars-experimental': ['error'], // '@typescript-eslint/no-unused-vars-experimental': ['error'],
}, },
}; };

File diff suppressed because it is too large Load Diff

View File

@ -30,16 +30,16 @@
"directory": "packages/api-redundancy" "directory": "packages/api-redundancy"
}, },
"devDependencies": { "devDependencies": {
"@types/jest": "^27.0.1", "@types/jest": "^27.4.0",
"@types/node": "^16.9.4", "@types/node": "^17.0.12",
"@typescript-eslint/eslint-plugin": "^4.31.1", "@typescript-eslint/eslint-plugin": "^5.10.1",
"@typescript-eslint/parser": "^4.31.1", "@typescript-eslint/parser": "^5.10.1",
"cross-env": "^7.0.3", "cross-env": "^7.0.3",
"eslint": "^7.32.0", "eslint": "^8.7.0",
"eslint-plugin-jasmine": "^4.1.2", "eslint-plugin-jasmine": "^4.1.3",
"jasmine": "^3.9.0", "jasmine": "^4.0.2",
"jest": "^27.2.0", "jest": "^27.4.7",
"ts-jest": "^27.0.5", "ts-jest": "^27.1.3",
"tsup": "^5.1.0" "tsup": "^5.11.11"
} }
} }

View File

@ -21,7 +21,7 @@ describe('Redundancy class', () => {
const query = redundancy.query( const query = redundancy.query(
'/foo', '/foo',
(resource, payload, status) => { (resource, payload, callback) => {
counter++; counter++;
expect(counter).toBeLessThan(3); // No more than 2 queries should be executed expect(counter).toBeLessThan(3); // No more than 2 queries should be executed
@ -34,7 +34,7 @@ describe('Redundancy class', () => {
} }
// Do something with "data", simulate instant callback // Do something with "data", simulate instant callback
status.done(responses[uri]); callback('success', responses[uri]);
// Complete test // Complete test
setTimeout(() => { setTimeout(() => {
@ -78,7 +78,9 @@ describe('Redundancy class', () => {
}; };
let counter = 0; let counter = 0;
const query = redundancy.query('/foo', (resource, payload, status) => { const query = redundancy.query(
'/foo',
(resource, payload, callback) => {
counter++; counter++;
expect(counter).toBeLessThan(2); // Should be success on first call because start index = 1 expect(counter).toBeLessThan(2); // Should be success on first call because start index = 1
@ -91,7 +93,7 @@ describe('Redundancy class', () => {
} }
// Do something with "data", simulate instant callback // Do something with "data", simulate instant callback
status.done(responses[uri]); callback('success', responses[uri]);
// Complete test // Complete test
setTimeout(() => { setTimeout(() => {
@ -100,7 +102,8 @@ describe('Redundancy class', () => {
expect(redundancy.getIndex()).toEqual(1); expect(redundancy.getIndex()).toEqual(1);
done(); done();
}); });
}); }
);
// Test find() // Test find()
expect(redundancy.find((item) => item().payload === '/foo')).toEqual( expect(redundancy.find((item) => item().payload === '/foo')).toEqual(

View File

@ -1,24 +1,3 @@
/**
* Callback for "timeout" configuration property.
* Returns number of milliseconds to wait before failing query, while there are pending resources.
*/
export interface TimeoutCallback {
(
startTime: number // Start time
): number;
}
/**
* Callback for "rotate" configuration property.
* Returns number of milliseconds to wait before trying next resource.
*/
export interface RotationTimeoutCallback {
(
queriesSent: number, // Number of queries sent, starts with 1 for timeout after first resource
startTime: number // Query start time
): number;
}
/** /**
* Resource to rotate (usually hostname or partial URL) * Resource to rotate (usually hostname or partial URL)
*/ */
@ -30,8 +9,8 @@ export type RedundancyResource = string;
export interface RedundancyConfig { export interface RedundancyConfig {
resources: RedundancyResource[]; // Resources to rotate resources: RedundancyResource[]; // Resources to rotate
index: number; // Start index index: number; // Start index
timeout: number | TimeoutCallback; // Timeout for error (full timeout = timeout + resources.length * rotate) timeout: number; // Timeout for error (full timeout = timeout + resources.length * rotate)
rotate: number | RotationTimeoutCallback; // Timeout for one query rotate: number; // Timeout for one query
random: boolean; // True if order should be randomised random: boolean; // True if order should be randomised
dataAfterTimeout: boolean; // True if data can be sent after timeout dataAfterTimeout: boolean; // True if data can be sent after timeout
} }

View File

@ -13,9 +13,9 @@ import { sendQuery } from './query';
export { GetQueryStatus, QueryModuleCallback, QueryDoneCallback }; export { GetQueryStatus, QueryModuleCallback, QueryDoneCallback };
export type { export type {
QueryAbortCallback, QueryAbortCallback,
QueryUpdateIndexCallback,
QueryStatus, QueryStatus,
PendingQueryItem, QueryModuleResponseType,
QueryModuleResponse,
} from './query'; } from './query';
/** /**
@ -117,10 +117,6 @@ export function initRedundancy(cfg: Partial<RedundancyConfig>): Redundancy {
if (doneCallback) { if (doneCallback) {
doneCallback(data, error); doneCallback(data, error);
} }
},
(newIndex) => {
// Update start index
config.index = newIndex;
} }
); );
queries.push(query); queries.push(query);

View File

@ -11,12 +11,20 @@ type QueryItemStatus = 'pending' | 'completed' | 'aborted' | 'failed';
*/ */
type QueryPayload = unknown; type QueryPayload = unknown;
/**
* Response from query module
*/
export type QueryModuleResponseData = unknown;
/** /**
* Callback * Callback
* *
* If error is present, something went wrong and data is undefined. If error is undefined, data is set. * If error is present, something went wrong and data is undefined. If error is undefined, data is set.
*/ */
export type QueryDoneCallback = (data?: unknown, error?: unknown) => void; export type QueryDoneCallback = (
data?: QueryModuleResponseData,
error?: QueryModuleResponseData
) => void;
/** /**
* Callback for "abort" pending item. * Callback for "abort" pending item.
@ -24,9 +32,13 @@ export type QueryDoneCallback = (data?: unknown, error?: unknown) => void;
export type QueryAbortCallback = () => void; export type QueryAbortCallback = () => void;
/** /**
* Callback to call to update last successful resource index. Used by Resundancy class to automatically update config. * Response from query module
*/ */
export type QueryUpdateIndexCallback = (index: number) => void; export type QueryModuleResponseType = 'success' | 'next' | 'abort';
export type QueryModuleResponse = (
status: QueryModuleResponseType,
data: QueryModuleResponseData
) => void;
/** /**
* Status for query * Status for query
@ -52,12 +64,10 @@ export type GetQueryStatus = () => QueryStatus;
/** /**
* Item in pending items list * Item in pending items list
*/ */
export interface PendingQueryItem { interface PendingQueryItem {
readonly getQueryStatus: GetQueryStatus;
status: QueryItemStatus; // Current query status status: QueryItemStatus; // Current query status
readonly resource: RedundancyResource; // Resource readonly resource: RedundancyResource; // Resource
readonly done: QueryDoneCallback; // Function to call with data readonly callback: QueryModuleResponse; // Function to call with data
abort?: QueryAbortCallback; // Function to call to abort query, set by query handler
} }
/** /**
@ -66,7 +76,7 @@ export interface PendingQueryItem {
export type QueryModuleCallback = ( export type QueryModuleCallback = (
resource: RedundancyResource, resource: RedundancyResource,
payload: QueryPayload, payload: QueryPayload,
queryItem: PendingQueryItem callback: QueryModuleResponse
) => void; ) => void;
/** /**
@ -74,10 +84,9 @@ export type QueryModuleCallback = (
*/ */
export function sendQuery( export function sendQuery(
config: RedundancyConfig, config: RedundancyConfig,
payload: unknown, payload: QueryPayload,
query: QueryModuleCallback, query: QueryModuleCallback,
done?: QueryDoneCallback, done?: QueryDoneCallback
success?: QueryUpdateIndexCallback
): GetQueryStatus { ): GetQueryStatus {
// Get number of resources // Get number of resources
const resourcesCount = config.resources.length; const resourcesCount = config.resources.length;
@ -110,7 +119,7 @@ export function sendQuery(
const startTime = Date.now(); const startTime = Date.now();
let status: QueryItemStatus = 'pending'; let status: QueryItemStatus = 'pending';
let queriesSent = 0; let queriesSent = 0;
let lastError: unknown = void 0; let lastError: QueryModuleResponseData | undefined;
// Timer // Timer
let timer: ReturnType<typeof setTimeout> | null = null; let timer: ReturnType<typeof setTimeout> | null = null;
@ -148,9 +157,6 @@ export function sendQuery(
// Abort all queued items // Abort all queued items
queue.forEach((item) => { queue.forEach((item) => {
if (item.abort) {
item.abort();
}
if (item.status === 'pending') { if (item.status === 'pending') {
item.status = 'aborted'; item.status = 'aborted';
} }
@ -205,15 +211,12 @@ export function sendQuery(
* Clear queue * Clear queue
*/ */
function clearQueue(): void { function clearQueue(): void {
queue = queue.filter((item) => { queue.forEach((item) => {
if (item.status === 'pending') { if (item.status === 'pending') {
item.status = 'aborted'; item.status = 'aborted';
} }
if (item.abort) {
item.abort();
}
return false;
}); });
queue = [];
} }
/** /**
@ -221,10 +224,10 @@ export function sendQuery(
*/ */
function moduleResponse( function moduleResponse(
item: PendingQueryItem, item: PendingQueryItem,
data?: unknown, response: QueryModuleResponseType,
error?: unknown data: QueryModuleResponseData
): void { ): void {
const isError = data === void 0; const isError = response !== 'success';
// Remove item from queue // Remove item from queue
queue = queue.filter((queued) => queued !== item); queue = queue.filter((queued) => queued !== item);
@ -248,11 +251,16 @@ export function sendQuery(
return; return;
} }
// Abort
if (response === 'abort') {
lastError = data;
failQuery();
return;
}
// Error // Error
if (isError) { if (isError) {
if (error !== void 0) { lastError = data;
lastError = error;
}
if (!queue.length) { if (!queue.length) {
if (!resources.length) { if (!resources.length) {
// Nothing else queued, nothing can be queued // Nothing else queued, nothing can be queued
@ -270,11 +278,11 @@ export function sendQuery(
resetTimer(); resetTimer();
clearQueue(); clearQueue();
// Update index in Redundancy // Update index in configuration
if (success && !config.random) { if (!config.random) {
const index = config.resources.indexOf(item.resource); const index = config.resources.indexOf(item.resource);
if (index !== -1 && index !== config.index) { if (index !== -1 && index !== config.index) {
success(index); config.index = index;
} }
} }
@ -302,11 +310,6 @@ export function sendQuery(
if (resource === void 0) { if (resource === void 0) {
// Nothing to execute: wait for final timeout before failing // Nothing to execute: wait for final timeout before failing
if (queue.length) { if (queue.length) {
const timeout: number =
typeof config.timeout === 'function'
? config.timeout(startTime)
: config.timeout;
if (timeout) {
// Last timeout before failing to allow late response // Last timeout before failing to allow late response
timer = setTimeout(() => { timer = setTimeout(() => {
resetTimer(); resetTimer();
@ -315,10 +318,9 @@ export function sendQuery(
clearQueue(); clearQueue();
failQuery(); failQuery();
} }
}, timeout); }, config.timeout);
return; return;
} }
}
// Fail // Fail
failQuery(); failQuery();
@ -327,11 +329,10 @@ export function sendQuery(
// Create new item // Create new item
const item: PendingQueryItem = { const item: PendingQueryItem = {
getQueryStatus,
status: 'pending', status: 'pending',
resource, resource,
done: (data?: unknown, error?: unknown) => { callback: (status, data) => {
moduleResponse(item, data, error); moduleResponse(item, status, data);
}, },
}; };
@ -341,17 +342,11 @@ export function sendQuery(
// Bump next index // Bump next index
queriesSent++; queriesSent++;
// Get timeout for next item
const timeout: number =
typeof config.rotate === 'function'
? config.rotate(queriesSent, startTime)
: config.rotate;
// Create timer // Create timer
timer = setTimeout(execNext, timeout); timer = setTimeout(execNext, config.rotate);
// Execute it // Execute it
query(resource, payload, item); query(resource, payload, item.callback);
} }
// Execute first query on next tick // Execute first query on next tick

View File

@ -1,5 +1,5 @@
import type { RedundancyConfig } from '../src/config'; import type { RedundancyConfig } from '../src/config';
import type { PendingQueryItem } from '../src/query'; import type { QueryModuleResponse } from '../src/query';
import { sendQuery } from '../src/query'; import { sendQuery } from '../src/query';
describe('Advanced queries with multiple resources', () => { describe('Advanced queries with multiple resources', () => {
@ -20,14 +20,13 @@ describe('Advanced queries with multiple resources', () => {
let isSync = true; let isSync = true;
const startTime = Date.now(); const startTime = Date.now();
let sentQuery = 0; let sentQuery = 0;
let itemAborted = false; let secondCallback: QueryModuleResponse;
let secondItem: PendingQueryItem;
// Send query // Send query
const getStatus = sendQuery( const getStatus = sendQuery(
config, config,
payload, payload,
(resource, queryPayload, queryItem) => { (resource, queryPayload, callback) => {
expect(isSync).toEqual(false); expect(isSync).toEqual(false);
expect(queryPayload).toEqual(payload); expect(queryPayload).toEqual(payload);
@ -36,7 +35,6 @@ describe('Advanced queries with multiple resources', () => {
expect(resource).toEqual(resources[sentQuery]); expect(resource).toEqual(resources[sentQuery]);
// Check status // Check status
expect(queryItem.getQueryStatus).toEqual(getStatus);
const status = getStatus(); const status = getStatus();
expect(status.status).toEqual('pending'); expect(status.status).toEqual('pending');
expect(status.payload).toEqual(payload); expect(status.payload).toEqual(payload);
@ -51,13 +49,6 @@ describe('Advanced queries with multiple resources', () => {
expect(status.queriesSent).toEqual(1); expect(status.queriesSent).toEqual(1);
expect(status.queriesPending).toEqual(1); expect(status.queriesPending).toEqual(1);
// Add abort
queryItem.abort = (): void => {
done(
'Abort should have not been called for first item'
);
};
// Fail in 20ms // Fail in 20ms
setTimeout(() => { setTimeout(() => {
// Status should not have changed // Status should not have changed
@ -66,7 +57,7 @@ describe('Advanced queries with multiple resources', () => {
expect(status.queriesPending).toEqual(1); expect(status.queriesPending).toEqual(1);
// Fail // Fail
queryItem.done(void 0, true); callback('next', true);
}, 20); }, 20);
return; return;
@ -75,15 +66,8 @@ describe('Advanced queries with multiple resources', () => {
expect(status.queriesSent).toEqual(2); expect(status.queriesSent).toEqual(2);
expect(status.queriesPending).toEqual(1); expect(status.queriesPending).toEqual(1);
// Add abort
queryItem.abort = (): void => {
done(
'Abort should have not been called for second item'
);
};
// Save item // Save item
secondItem = queryItem; secondCallback = callback;
return; return;
case 3: case 3:
@ -91,16 +75,8 @@ describe('Advanced queries with multiple resources', () => {
expect(status.queriesSent).toEqual(3); expect(status.queriesSent).toEqual(3);
expect(status.queriesPending).toEqual(2); expect(status.queriesPending).toEqual(2);
// Add abort
queryItem.abort = (): void => {
// This item should be aborted, but only once
expect(itemAborted).toEqual(false);
expect(sentQuery).toEqual(3);
itemAborted = true;
};
// Complete second item // Complete second item
secondItem.done(result); secondCallback('success', result);
return; return;
default: default:
@ -111,9 +87,6 @@ describe('Advanced queries with multiple resources', () => {
// Make sure queries were sent // Make sure queries were sent
expect(sentQuery).toEqual(3); expect(sentQuery).toEqual(3);
// Third query should have been aborted
expect(itemAborted).toEqual(true);
// Validate data // Validate data
expect(data).toEqual(result); expect(data).toEqual(result);
expect(error).toBeUndefined(); expect(error).toBeUndefined();
@ -159,14 +132,13 @@ describe('Advanced queries with multiple resources', () => {
let isSync = true; let isSync = true;
const startTime = Date.now(); const startTime = Date.now();
let sentQuery = 0; let sentQuery = 0;
let itemAborted = false; let firstCallback: QueryModuleResponse;
let firstItem: PendingQueryItem;
// Send query // Send query
const getStatus = sendQuery( const getStatus = sendQuery(
config, config,
payload, payload,
(resource, queryPayload, queryItem) => { (resource, queryPayload, callback) => {
expect(isSync).toEqual(false); expect(isSync).toEqual(false);
expect(queryPayload).toEqual(payload); expect(queryPayload).toEqual(payload);
@ -175,7 +147,6 @@ describe('Advanced queries with multiple resources', () => {
expect(resource).toEqual(resources[sentQuery]); expect(resource).toEqual(resources[sentQuery]);
// Check status // Check status
expect(queryItem.getQueryStatus).toEqual(getStatus);
const status = getStatus(); const status = getStatus();
expect(status.status).toEqual('pending'); expect(status.status).toEqual('pending');
expect(status.payload).toEqual(payload); expect(status.payload).toEqual(payload);
@ -190,15 +161,8 @@ describe('Advanced queries with multiple resources', () => {
expect(status.queriesSent).toEqual(1); expect(status.queriesSent).toEqual(1);
expect(status.queriesPending).toEqual(1); expect(status.queriesPending).toEqual(1);
// Add abort // Store callback
queryItem.abort = (): void => { firstCallback = callback;
done(
'Abort should have not been called for first item'
);
};
// Store item
firstItem = queryItem;
return; return;
case 2: case 2:
@ -206,12 +170,6 @@ describe('Advanced queries with multiple resources', () => {
expect(status.queriesSent).toEqual(2); expect(status.queriesSent).toEqual(2);
expect(status.queriesPending).toEqual(2); expect(status.queriesPending).toEqual(2);
// Add abort
queryItem.abort = (): void => {
expect(itemAborted).toEqual(false);
itemAborted = true;
};
// Complete first item in 20ms (70ms from start), then second item // Complete first item in 20ms (70ms from start), then second item
setTimeout(() => { setTimeout(() => {
// Check status // Check status
@ -220,14 +178,12 @@ describe('Advanced queries with multiple resources', () => {
expect(status.queriesSent).toEqual(2); expect(status.queriesSent).toEqual(2);
expect(status.queriesPending).toEqual(2); expect(status.queriesPending).toEqual(2);
firstItem.done(result1); firstCallback('success', result1);
// Complete second item in 30 ms // Complete second item in 30 ms
setTimeout(() => { setTimeout(() => {
expect(queryItem.status).toEqual('aborted');
// Should not change anything because query is already complete // Should not change anything because query is already complete
queryItem.done(result2); callback('success', result2);
// Finish test // Finish test
done(); done();
@ -243,9 +199,6 @@ describe('Advanced queries with multiple resources', () => {
// Make sure queries were sent // Make sure queries were sent
expect(sentQuery).toEqual(2); expect(sentQuery).toEqual(2);
// Second query should have been aborted
expect(itemAborted).toEqual(true);
// Validate data // Validate data
expect(data).toEqual(result1); expect(data).toEqual(result1);
expect(error).toBeUndefined(); expect(error).toBeUndefined();
@ -290,14 +243,14 @@ describe('Advanced queries with multiple resources', () => {
let isSync = true; let isSync = true;
const startTime = Date.now(); const startTime = Date.now();
let sentQuery = 0; let sentQuery = 0;
let firstItem: PendingQueryItem; let firstCallback: QueryModuleResponse;
let completeCount = 0; let completeCount = 0;
// Send query // Send query
const getStatus = sendQuery( const getStatus = sendQuery(
config, config,
payload, payload,
(resource, queryPayload, queryItem) => { (resource, queryPayload, callback) => {
expect(isSync).toEqual(false); expect(isSync).toEqual(false);
expect(queryPayload).toEqual(payload); expect(queryPayload).toEqual(payload);
@ -306,7 +259,6 @@ describe('Advanced queries with multiple resources', () => {
expect(resource).toEqual(resources[sentQuery]); expect(resource).toEqual(resources[sentQuery]);
// Check status // Check status
expect(queryItem.getQueryStatus).toEqual(getStatus);
const status = getStatus(); const status = getStatus();
expect(status.status).toEqual('pending'); expect(status.status).toEqual('pending');
expect(status.payload).toEqual(payload); expect(status.payload).toEqual(payload);
@ -321,8 +273,8 @@ describe('Advanced queries with multiple resources', () => {
expect(status.queriesSent).toEqual(1); expect(status.queriesSent).toEqual(1);
expect(status.queriesPending).toEqual(1); expect(status.queriesPending).toEqual(1);
// Store item // Store callback
firstItem = queryItem; firstCallback = callback;
return; return;
case 2: case 2:
@ -360,7 +312,7 @@ describe('Advanced queries with multiple resources', () => {
expect(diff > 130 && diff < 170).toEqual(true); expect(diff > 130 && diff < 170).toEqual(true);
// Send data from first query, which should be ignored because dataAfterTimeout is false // Send data from first query, which should be ignored because dataAfterTimeout is false
firstItem.done(result); firstCallback('success', result);
// Complete test // Complete test
done(); done();
@ -399,14 +351,14 @@ describe('Advanced queries with multiple resources', () => {
let isSync = true; let isSync = true;
const startTime = Date.now(); const startTime = Date.now();
let sentQuery = 0; let sentQuery = 0;
let firstItem: PendingQueryItem; let firstCallback: QueryModuleResponse;
let completeCount = 0; let completeCount = 0;
// Send query // Send query
const getStatus = sendQuery( const getStatus = sendQuery(
config, config,
payload, payload,
(resource, queryPayload, queryItem) => { (resource, queryPayload, callback) => {
expect(isSync).toEqual(false); expect(isSync).toEqual(false);
expect(queryPayload).toEqual(payload); expect(queryPayload).toEqual(payload);
@ -415,7 +367,6 @@ describe('Advanced queries with multiple resources', () => {
expect(resource).toEqual(resources[sentQuery]); expect(resource).toEqual(resources[sentQuery]);
// Check status // Check status
expect(queryItem.getQueryStatus).toEqual(getStatus);
const status = getStatus(); const status = getStatus();
expect(status.status).toEqual('pending'); expect(status.status).toEqual('pending');
expect(status.payload).toEqual(payload); expect(status.payload).toEqual(payload);
@ -430,8 +381,8 @@ describe('Advanced queries with multiple resources', () => {
expect(status.queriesSent).toEqual(1); expect(status.queriesSent).toEqual(1);
expect(status.queriesPending).toEqual(1); expect(status.queriesPending).toEqual(1);
// Store item // Store callback
firstItem = queryItem; firstCallback = callback;
return; return;
case 2: case 2:
@ -469,7 +420,7 @@ describe('Advanced queries with multiple resources', () => {
expect(diff > 130 && diff < 170).toEqual(true); expect(diff > 130 && diff < 170).toEqual(true);
// Send data from first query // Send data from first query
firstItem.done(result); firstCallback('success', result);
})(); })();
return; return;

View File

@ -74,7 +74,7 @@ describe('Basic queries', () => {
const getStatus = sendQuery( const getStatus = sendQuery(
config, config,
payload, payload,
(resource, queryPayload, queryItem) => { (resource, queryPayload, callback) => {
expect(isSync).toEqual(false); expect(isSync).toEqual(false);
expect(resource).toEqual(resources[0]); expect(resource).toEqual(resources[0]);
expect(queryPayload).toEqual(payload); expect(queryPayload).toEqual(payload);
@ -84,20 +84,14 @@ describe('Basic queries', () => {
sentQuery = true; sentQuery = true;
// Check status // Check status
expect(queryItem.getQueryStatus).toEqual(getStatus);
const status = getStatus(); const status = getStatus();
expect(status.status).toEqual('pending'); expect(status.status).toEqual('pending');
expect(status.payload).toEqual(payload); expect(status.payload).toEqual(payload);
expect(status.queriesSent).toEqual(1); expect(status.queriesSent).toEqual(1);
expect(status.queriesPending).toEqual(1); expect(status.queriesPending).toEqual(1);
// Add abort function
queryItem.abort = (): void => {
done('Abort should have not been called');
};
// Complete // Complete
queryItem.done(result); callback('success', result);
}, },
(data, error) => { (data, error) => {
// Make sure query was sent // Make sure query was sent
@ -152,7 +146,7 @@ describe('Basic queries', () => {
const getStatus = sendQuery( const getStatus = sendQuery(
config, config,
payload, payload,
(resource, queryPayload, queryItem) => { (resource, queryPayload, callback) => {
expect(isSync).toEqual(false); expect(isSync).toEqual(false);
expect(resource).toEqual(resources[0]); expect(resource).toEqual(resources[0]);
expect(queryPayload).toEqual(payload); expect(queryPayload).toEqual(payload);
@ -161,13 +155,8 @@ describe('Basic queries', () => {
expect(sentQuery).toEqual(false); expect(sentQuery).toEqual(false);
sentQuery = true; sentQuery = true;
// Add abort function
queryItem.abort = (): void => {
done('Abort should have not been called');
};
// Fail // Fail
queryItem.done(void 0, result); callback('next', result);
}, },
(data, error) => { (data, error) => {
// Make sure query was sent // Make sure query was sent
@ -216,13 +205,12 @@ describe('Basic queries', () => {
let isSync = true; let isSync = true;
const startTime = Date.now(); const startTime = Date.now();
let sentQuery = false; let sentQuery = false;
let itemAborted = false;
// Send query // Send query
const getStatus = sendQuery( const getStatus = sendQuery(
config, config,
payload, payload,
(resource, queryPayload, queryItem) => { (resource, queryPayload) => {
expect(isSync).toEqual(false); expect(isSync).toEqual(false);
expect(resource).toEqual(resources[0]); expect(resource).toEqual(resources[0]);
expect(queryPayload).toEqual(payload); expect(queryPayload).toEqual(payload);
@ -231,12 +219,6 @@ describe('Basic queries', () => {
expect(sentQuery).toEqual(false); expect(sentQuery).toEqual(false);
sentQuery = true; sentQuery = true;
// Add abort function
queryItem.abort = (): void => {
expect(itemAborted).toEqual(false);
itemAborted = true;
};
// Do not do anything // Do not do anything
}, },
(data, error) => { (data, error) => {
@ -253,9 +235,6 @@ describe('Basic queries', () => {
expect(status.queriesSent).toEqual(1); expect(status.queriesSent).toEqual(1);
expect(status.queriesPending).toEqual(0); expect(status.queriesPending).toEqual(0);
// Item should have been aborted
expect(itemAborted).toEqual(true);
// Should have been config.rotate + config.timeout // Should have been config.rotate + config.timeout
const diff = Date.now() - startTime; const diff = Date.now() - startTime;
expect(diff).toBeGreaterThan(250); expect(diff).toBeGreaterThan(250);
@ -272,4 +251,63 @@ describe('Basic queries', () => {
isSync = false; isSync = false;
}); });
it('Abort query', (done) => {
const payload = {};
const resources = ['api1', 'api2', 'api3'];
const config: RedundancyConfig = {
resources,
index: 0,
timeout: 200,
rotate: 100,
random: false,
dataAfterTimeout: false,
};
// Tracking
let isSync = true;
let sentQuery = false;
// Send query
const getStatus = sendQuery(
config,
payload,
(resource, queryPayload, callback) => {
expect(isSync).toEqual(false);
expect(resource).toEqual(resources[0]);
expect(queryPayload).toEqual(payload);
// Make sure query was executed only once
expect(sentQuery).toEqual(false);
sentQuery = true;
// Abort
callback('abort', 404);
},
(data, error) => {
// Make sure query was sent
expect(sentQuery).toEqual(true);
// Validate data
expect(data).toBeUndefined();
expect(error).toBe(404);
// Check status
const status = getStatus();
expect(status.status).toEqual('failed');
expect(status.queriesSent).toEqual(1);
expect(status.queriesPending).toEqual(0);
done();
}
);
// Check status
const status = getStatus();
expect(status.status).toEqual('pending');
expect(status.queriesSent).toEqual(0);
expect(status.queriesPending).toEqual(0);
isSync = false;
});
}); });

View File

@ -24,7 +24,7 @@ describe('Multiple resources', () => {
const getStatus = sendQuery( const getStatus = sendQuery(
config, config,
payload, payload,
(resource, queryPayload, queryItem) => { (resource, queryPayload, callback) => {
expect(isSync).toEqual(false); expect(isSync).toEqual(false);
expect(resource).toEqual('api1'); expect(resource).toEqual('api1');
expect(queryPayload).toEqual(payload); expect(queryPayload).toEqual(payload);
@ -34,20 +34,14 @@ describe('Multiple resources', () => {
sentQuery++; sentQuery++;
// Check status // Check status
expect(queryItem.getQueryStatus).toEqual(getStatus);
const status = getStatus(); const status = getStatus();
expect(status.status).toEqual('pending'); expect(status.status).toEqual('pending');
expect(status.payload).toEqual(payload); expect(status.payload).toEqual(payload);
expect(status.queriesSent).toEqual(1); expect(status.queriesSent).toEqual(1);
expect(status.queriesPending).toEqual(1); expect(status.queriesPending).toEqual(1);
// Add abort function
queryItem.abort = (): void => {
done('Abort should have not been called');
};
// Complete // Complete
queryItem.done(result); callback('success', result);
}, },
(data, error) => { (data, error) => {
// Make sure query was sent // Make sure query was sent
@ -68,9 +62,6 @@ describe('Multiple resources', () => {
expect(diff).toBeLessThan(50); expect(diff).toBeLessThan(50);
done(); done();
},
() => {
done('This should not have been called');
} }
); );
@ -100,14 +91,12 @@ describe('Multiple resources', () => {
let isSync = true; let isSync = true;
const startTime = Date.now(); const startTime = Date.now();
let sentQuery = 0; let sentQuery = 0;
let itemAborted = false;
let parentUpdated = false;
// Send query // Send query
const getStatus = sendQuery( const getStatus = sendQuery(
config, config,
payload, payload,
(resource, queryPayload, queryItem) => { (resource, queryPayload, callback) => {
expect(isSync).toEqual(false); expect(isSync).toEqual(false);
expect(queryPayload).toEqual(payload); expect(queryPayload).toEqual(payload);
@ -116,7 +105,6 @@ describe('Multiple resources', () => {
expect(resource).toEqual(resources[sentQuery]); expect(resource).toEqual(resources[sentQuery]);
// Check status // Check status
expect(queryItem.getQueryStatus).toEqual(getStatus);
const status = getStatus(); const status = getStatus();
expect(status.status).toEqual('pending'); expect(status.status).toEqual('pending');
expect(status.payload).toEqual(payload); expect(status.payload).toEqual(payload);
@ -128,29 +116,15 @@ describe('Multiple resources', () => {
expect(status.queriesSent).toEqual(sentQuery); expect(status.queriesSent).toEqual(sentQuery);
expect(status.queriesPending).toEqual(sentQuery); expect(status.queriesPending).toEqual(sentQuery);
// Add abort function
// Time out first, complete second // Time out first, complete second
switch (sentQuery) { switch (sentQuery) {
case 1: case 1:
queryItem.abort = (): void => {
// First item should be aborted, but only once
expect(itemAborted).toEqual(false);
// When this is executed, counter should have been increased
expect(sentQuery).toEqual(2);
itemAborted = true;
// Do nothing, let it time out // Do nothing, let it time out
};
return; return;
case 2: case 2:
queryItem.abort = (): void => {
done('Abort should have not been called');
};
// Send result // Send result
queryItem.done(result); callback('success', result);
return; return;
default: default:
@ -161,9 +135,6 @@ describe('Multiple resources', () => {
// Make sure queries were sent // Make sure queries were sent
expect(sentQuery).toEqual(2); expect(sentQuery).toEqual(2);
// First query should have been aborted
expect(itemAborted).toEqual(true);
// Validate data // Validate data
expect(data).toEqual(result); expect(data).toEqual(result);
expect(error).toBeUndefined(); expect(error).toBeUndefined();
@ -174,20 +145,12 @@ describe('Multiple resources', () => {
expect(status.queriesSent).toEqual(2); expect(status.queriesSent).toEqual(2);
expect(status.queriesPending).toEqual(0); expect(status.queriesPending).toEqual(0);
// Parent should have been updated
expect(parentUpdated).toEqual(true);
// Delay between first and second queries // Delay between first and second queries
const diff = Date.now() - startTime; const diff = Date.now() - startTime;
expect(diff).toBeGreaterThan(50); expect(diff).toBeGreaterThan(50);
expect(diff).toBeLessThan(150); expect(diff).toBeLessThan(150);
done(); done();
},
(newIndex) => {
// Start index should be updated to 1
expect(newIndex).toEqual(1);
parentUpdated = true;
} }
); );
@ -216,14 +179,12 @@ describe('Multiple resources', () => {
let isSync = true; let isSync = true;
const startTime = Date.now(); const startTime = Date.now();
let sentQuery = 0; let sentQuery = 0;
let item1Aborted = false;
let item2Aborted = false;
// Send query // Send query
const getStatus = sendQuery( const getStatus = sendQuery(
config, config,
payload, payload,
(resource, queryPayload, queryItem) => { (resource, queryPayload, callback) => {
expect(isSync).toEqual(false); expect(isSync).toEqual(false);
expect(queryPayload).toEqual(payload); expect(queryPayload).toEqual(payload);
@ -232,7 +193,6 @@ describe('Multiple resources', () => {
expect(resource).toEqual(resources[sentQuery]); expect(resource).toEqual(resources[sentQuery]);
// Check status // Check status
expect(queryItem.getQueryStatus).toEqual(getStatus);
const status = getStatus(); const status = getStatus();
expect(status.status).toEqual('pending'); expect(status.status).toEqual('pending');
expect(status.payload).toEqual(payload); expect(status.payload).toEqual(payload);
@ -247,29 +207,8 @@ describe('Multiple resources', () => {
// Add abort functions // Add abort functions
switch (sentQuery) { switch (sentQuery) {
case 1: case 1:
queryItem.abort = (): void => {
expect(item1Aborted).toEqual(false);
expect(item2Aborted).toEqual(false);
// This should have been executed at the end
expect(sentQuery).toEqual(2);
item1Aborted = true;
// Do not send anything
};
return;
case 2: case 2:
queryItem.abort = (): void => {
expect(item1Aborted).toEqual(true);
expect(item2Aborted).toEqual(false);
// This should have been executed at the end
expect(sentQuery).toEqual(2);
item2Aborted = true;
// Do not send anything // Do not send anything
};
return; return;
default: default:
@ -280,10 +219,6 @@ describe('Multiple resources', () => {
// Make sure queries were sent // Make sure queries were sent
expect(sentQuery).toEqual(2); expect(sentQuery).toEqual(2);
// Queries should have been aborted
expect(item1Aborted).toEqual(true);
expect(item2Aborted).toEqual(true);
// Validate data // Validate data
expect(data).toBeUndefined(); expect(data).toBeUndefined();
expect(error).toBeUndefined(); expect(error).toBeUndefined();
@ -300,9 +235,6 @@ describe('Multiple resources', () => {
expect(diff).toBeLessThan(120); expect(diff).toBeLessThan(120);
done(); done();
},
() => {
done('This should have never been called');
} }
); );
@ -331,14 +263,12 @@ describe('Multiple resources', () => {
let isSync = true; let isSync = true;
const startTime = Date.now(); const startTime = Date.now();
let sentQuery = 0; let sentQuery = 0;
let item1Aborted = false;
let item2Aborted = false;
// Send query // Send query
const getStatus = sendQuery( const getStatus = sendQuery(
config, config,
payload, payload,
(resource, queryPayload, queryItem) => { (resource, queryPayload) => {
expect(isSync).toEqual(false); expect(isSync).toEqual(false);
expect(queryPayload).toEqual(payload); expect(queryPayload).toEqual(payload);
@ -348,33 +278,11 @@ describe('Multiple resources', () => {
// Bump counter // Bump counter
sentQuery++; sentQuery++;
// Add abort functions
switch (sentQuery) {
case 1:
queryItem.abort = (): void => {
item1Aborted = true;
};
return;
case 2:
queryItem.abort = (): void => {
item2Aborted = true;
};
return;
default:
done('This code should not have been reached');
}
}, },
(data, error) => { (data, error) => {
// Make sure queries were sent // Make sure queries were sent
expect(sentQuery).toEqual(2); expect(sentQuery).toEqual(2);
// Queries should have been aborted
expect(item1Aborted).toEqual(true);
expect(item2Aborted).toEqual(true);
// Validate data // Validate data
expect(data).toBeUndefined(); expect(data).toBeUndefined();
expect(error).toBeUndefined(); expect(error).toBeUndefined();
@ -385,9 +293,6 @@ describe('Multiple resources', () => {
expect(diff).toBeLessThan(120); expect(diff).toBeLessThan(120);
done(); done();
},
() => {
done('This should have never been called');
} }
); );
@ -448,9 +353,6 @@ describe('Multiple resources', () => {
expect(diff).toBeLessThan(170); expect(diff).toBeLessThan(170);
done(); done();
},
() => {
done('This should have never been called');
} }
); );

View File

@ -23,7 +23,7 @@ describe('Redundancy class', () => {
const query = redundancy.query( const query = redundancy.query(
'/foo', '/foo',
(resource, payload, status) => { (resource, payload, callback) => {
counter++; counter++;
expect(counter).toBeLessThan(3); // No more than 2 queries should be executed expect(counter).toBeLessThan(3); // No more than 2 queries should be executed
@ -36,7 +36,7 @@ describe('Redundancy class', () => {
} }
// Do something with "data", simulate instant callback // Do something with "data", simulate instant callback
status.done(responses[uri]); callback('success', responses[uri]);
// Complete test // Complete test
setTimeout(() => { setTimeout(() => {
@ -80,7 +80,9 @@ describe('Redundancy class', () => {
}; };
let counter = 0; let counter = 0;
const query = redundancy.query('/foo', (resource, payload, status) => { const query = redundancy.query(
'/foo',
(resource, payload, callback) => {
counter++; counter++;
expect(counter).toBeLessThan(2); // Should be success on first call because start index = 1 expect(counter).toBeLessThan(2); // Should be success on first call because start index = 1
@ -93,7 +95,7 @@ describe('Redundancy class', () => {
} }
// Do something with "data", simulate instant callback // Do something with "data", simulate instant callback
status.done(responses[uri]); callback('success', responses[uri]);
// Complete test // Complete test
setTimeout(() => { setTimeout(() => {
@ -102,7 +104,8 @@ describe('Redundancy class', () => {
expect(redundancy.getIndex()).toEqual(1); expect(redundancy.getIndex()).toEqual(1);
done(); done();
}); });
}); }
);
// Test find() // Test find()
expect(redundancy.find((item) => item().payload === '/foo')).toEqual( expect(redundancy.find((item) => item().payload === '/foo')).toEqual(

View File

@ -46,7 +46,7 @@ describe('Testing API loadIcons', () => {
return [item]; return [item];
}; };
const sendQuery = (host, params, item) => { const sendQuery = (host, params, callback) => {
// This callback should be called after prepareQuery // This callback should be called after prepareQuery
expect(asyncCounter).toBe(2); expect(asyncCounter).toBe(2);
asyncCounter++; asyncCounter++;
@ -64,7 +64,7 @@ describe('Testing API loadIcons', () => {
expect(params).toEqual(expected); expect(params).toEqual(expected);
// Send data // Send data
item.done({ callback('success', {
prefix, prefix,
icons: { icons: {
icon1: { icon1: {
@ -170,7 +170,7 @@ describe('Testing API loadIcons', () => {
}; };
let queryCounter = 0; let queryCounter = 0;
const sendQuery = (host, params, item) => { const sendQuery = (host, params, callback) => {
// Test input // Test input
expect(host).toBe('https://api1.local'); expect(host).toBe('https://api1.local');
@ -196,7 +196,7 @@ describe('Testing API loadIcons', () => {
body: '<path d="" />', body: '<path d="" />',
}; };
}); });
item.done({ callback('success', {
prefix, prefix,
icons, icons,
// Test mismatched provider: should be ignored because provider name is not affected by actual API response // Test mismatched provider: should be ignored because provider name is not affected by actual API response
@ -263,7 +263,7 @@ describe('Testing API loadIcons', () => {
}; };
let queryCounter = 0; let queryCounter = 0;
const sendQuery = (host, params, item) => { const sendQuery = (host, params, callback) => {
queryCounter++; queryCounter++;
params; params;
switch (queryCounter) { switch (queryCounter) {
@ -279,7 +279,7 @@ describe('Testing API loadIcons', () => {
expect(host).toBe('https://api2.local'); expect(host).toBe('https://api2.local');
// Return result // Return result
item.done({ callback('success', {
prefix, prefix,
icons: { icons: {
icon1: { icon1: {
@ -359,7 +359,7 @@ describe('Testing API loadIcons', () => {
}; };
let queryCounter = 0; let queryCounter = 0;
const sendQuery = (host, params, item) => { const sendQuery = (host, params, callback) => {
queryCounter++; queryCounter++;
expect(params.type).toBe('icons'); expect(params.type).toBe('icons');
@ -382,7 +382,7 @@ describe('Testing API loadIcons', () => {
expect(host).toBe('https://api2.local'); expect(host).toBe('https://api2.local');
// Return result // Return result
item.done({ callback('success', {
prefix, prefix,
icons: { icons: {
icon1: { icon1: {
@ -401,7 +401,7 @@ describe('Testing API loadIcons', () => {
expect(host).toBe('https://api2.local'); expect(host).toBe('https://api2.local');
// Return result // Return result
item.done({ callback('success', {
prefix, prefix,
icons: { icons: {
icon3: { icon3: {
@ -514,7 +514,7 @@ describe('Testing API loadIcons', () => {
}; };
let queryCounter = 0; let queryCounter = 0;
const sendQuery = (host, params, item) => { const sendQuery = (host, params, callback) => {
queryCounter++; queryCounter++;
expect(params.type).toBe('icons'); expect(params.type).toBe('icons');
@ -539,7 +539,7 @@ describe('Testing API loadIcons', () => {
expect(host).toBe('https://api2.local'); expect(host).toBe('https://api2.local');
// Return result // Return result
item.done({ callback('success', {
prefix: params.prefix, prefix: params.prefix,
icons: { icons: {
icon1: { icon1: {
@ -559,7 +559,7 @@ describe('Testing API loadIcons', () => {
expect(host).toBe('https://api2.local'); expect(host).toBe('https://api2.local');
// Return result // Return result
item.done({ callback('success', {
prefix: params.prefix, prefix: params.prefix,
icons: { icons: {
icon2: { icon2: {

View File

@ -1,4 +1,4 @@
import type { PendingQueryItem } from '@iconify/api-redundancy'; import type { QueryModuleResponse } from '@iconify/api-redundancy';
/** /**
* Params for sendQuery() * Params for sendQuery()
@ -31,7 +31,7 @@ export type IconifyAPIPrepareIconsQuery = (
export type IconifyAPISendQuery = ( export type IconifyAPISendQuery = (
host: string, host: string,
params: IconifyAPIQueryParams, params: IconifyAPIQueryParams,
status: PendingQueryItem callback: QueryModuleResponse
) => void; ) => void;
/** /**

View File

@ -1,4 +1,4 @@
import type { PendingQueryItem } from '@iconify/api-redundancy'; import type { QueryModuleResponse } from '@iconify/api-redundancy';
import type { import type {
IconifyAPIQueryParams, IconifyAPIQueryParams,
IconifyAPIPrepareIconsQuery, IconifyAPIPrepareIconsQuery,
@ -106,6 +106,13 @@ function calculateMaxLength(provider: string, prefix: string): number {
return result; return result;
} }
/**
* Should query be aborted, based on last HTTP status
*/
function shouldAbort(status: number): boolean {
return status === 404;
}
/** /**
* Prepare params * Prepare params
*/ */
@ -178,11 +185,11 @@ function getPath(provider?: string): string {
const send: IconifyAPISendQuery = ( const send: IconifyAPISendQuery = (
host: string, host: string,
params: IconifyAPIQueryParams, params: IconifyAPIQueryParams,
status: PendingQueryItem callback: QueryModuleResponse
): void => { ): void => {
if (!fetchModule) { if (!fetchModule) {
// Fail: return "424 Failed Dependency" (its not meant to be used like that, but it is the closest match) // Fail: return "424 Failed Dependency" (its not meant to be used like that, but it is the closest match)
status.done(void 0, 424); callback('abort', 424);
return; return;
} }
@ -208,7 +215,7 @@ const send: IconifyAPISendQuery = (
default: default:
// Fail: return 400 Bad Request // Fail: return 400 Bad Request
status.done(void 0, 400); callback('abort', 400);
return; return;
} }
@ -218,10 +225,11 @@ const send: IconifyAPISendQuery = (
// console.log('API query:', host + path); // console.log('API query:', host + path);
fetchModule(host + path) fetchModule(host + path)
.then((response) => { .then((response) => {
if (response.status !== 200) { const status = response.status;
if (status !== 200) {
setTimeout(() => { setTimeout(() => {
// Complete on next tick to get out of try...catch // Complete on next tick to get out of try...catch
status.done(void 0, response.status); callback(shouldAbort(status) ? 'abort' : 'next', status);
}); });
return; return;
} }
@ -234,7 +242,7 @@ const send: IconifyAPISendQuery = (
if (typeof data !== 'object' || data === null) { if (typeof data !== 'object' || data === null) {
setTimeout(() => { setTimeout(() => {
// Complete on next tick to get out of try...catch // Complete on next tick to get out of try...catch
status.done(void 0, defaultError); callback('next', defaultError);
}); });
return; return;
} }
@ -242,11 +250,11 @@ const send: IconifyAPISendQuery = (
// Store cache and complete on next tick // Store cache and complete on next tick
setTimeout(() => { setTimeout(() => {
// Complete on next tick to get out of try...catch // Complete on next tick to get out of try...catch
status.done(data); callback('success', data);
}); });
}) })
.catch(() => { .catch(() => {
status.done(void 0, defaultError); callback('next', defaultError);
}); });
}; };

View File

@ -1,4 +1,4 @@
import type { PendingQueryItem } from '@iconify/api-redundancy'; import type { QueryModuleResponse } from '@iconify/api-redundancy';
import type { import type {
IconifyAPIQueryParams, IconifyAPIQueryParams,
IconifyAPIPrepareIconsQuery, IconifyAPIPrepareIconsQuery,
@ -104,6 +104,7 @@ function calculateMaxLength(provider: string, prefix: string): number {
// Get available length // Get available length
const url = mergeParams(prefix + '.js', { const url = mergeParams(prefix + '.js', {
icons: '', icons: '',
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
callback: rootVarName!, callback: rootVarName!,
}); });
result = result =
@ -170,11 +171,11 @@ const prepare: IconifyAPIPrepareIconsQuery = (
const send: IconifyAPISendQuery = ( const send: IconifyAPISendQuery = (
host: string, host: string,
params: IconifyAPIQueryParams, params: IconifyAPIQueryParams,
status: PendingQueryItem callback: QueryModuleResponse
): void => { ): void => {
if (params.type !== 'icons') { if (params.type !== 'icons') {
// JSONP supports only icons // JSONP supports only icons
status.done(void 0, 400); callback('abort', 400);
return; return;
} }
@ -201,6 +202,7 @@ const send: IconifyAPISendQuery = (
const url = mergeParams(prefix + '.js', { const url = mergeParams(prefix + '.js', {
icons: iconsList, icons: iconsList,
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
callback: rootVarName!.replace('{cb}', callbackName), callback: rootVarName!.replace('{cb}', callbackName),
}); });
const path = pathCache[cacheKey] + url; const path = pathCache[cacheKey] + url;
@ -208,7 +210,7 @@ const send: IconifyAPISendQuery = (
global[callbackName] = (data: unknown): void => { global[callbackName] = (data: unknown): void => {
// Remove callback and complete query // Remove callback and complete query
delete global[callbackName]; delete global[callbackName];
status.done(data); callback('success', data);
}; };
// Create URI // Create URI

View File

@ -1,6 +1,5 @@
/* eslint-disable @typescript-eslint/no-unused-vars-experimental */
/* eslint-disable @typescript-eslint/no-unused-vars */ /* eslint-disable @typescript-eslint/no-unused-vars */
import type { PendingQueryItem } from '@iconify/api-redundancy'; import type { QueryModuleResponse } from '@iconify/api-redundancy';
import type { import type {
IconifyAPIIconsQueryParams, IconifyAPIIconsQueryParams,
IconifyAPIQueryParams, IconifyAPIQueryParams,
@ -252,7 +251,7 @@ export const mockAPIModule: IconifyAPIModule = {
send: ( send: (
host: string, host: string,
params: IconifyAPIQueryParams, params: IconifyAPIQueryParams,
status: PendingQueryItem queryCallback: QueryModuleResponse
) => { ) => {
const provider = params.provider; const provider = params.provider;
let data: IconifyMockAPI; let data: IconifyMockAPI;
@ -261,7 +260,7 @@ export const mockAPIModule: IconifyAPIModule = {
case 'icons': { case 'icons': {
if (provider === void 0) { if (provider === void 0) {
// Fail: return 400 Bad Request // Fail: return 400 Bad Request
status.done(void 0, 400); queryCallback('abort', 400);
return; return;
} }
const index = (params as MockAPIIconsQueryParams).index; const index = (params as MockAPIIconsQueryParams).index;
@ -280,12 +279,12 @@ export const mockAPIModule: IconifyAPIModule = {
default: default:
// Fail: return 400 Bad Request // Fail: return 400 Bad Request
status.done(void 0, 400); queryCallback('abort', 400);
return; return;
} }
if (data === void 0) { if (data === void 0) {
status.done(void 0, 404); queryCallback('abort', 404);
return; return;
} }
@ -308,11 +307,10 @@ export const mockAPIModule: IconifyAPIModule = {
// Run after delay // Run after delay
callback(() => { callback(() => {
if (typeof data.response === 'number') { queryCallback(
status.done(void 0, data.response); typeof data.response === 'number' ? 'next' : 'success',
} else { data.response
status.done(data.response); );
}
}); });
}, },
}; };

View File

@ -1,4 +1,4 @@
import type { PendingQueryItem } from '@iconify/api-redundancy'; import type { QueryModuleResponse } from '@iconify/api-redundancy';
import { addAPIProvider } from '../../lib/api/config'; import { addAPIProvider } from '../../lib/api/config';
import type { import type {
IconifyAPIIconsQueryParams, IconifyAPIIconsQueryParams,
@ -58,7 +58,7 @@ describe('Testing API loadIcons', () => {
const sendQuery = ( const sendQuery = (
host: string, host: string,
params: IconifyAPIQueryParams, params: IconifyAPIQueryParams,
item: PendingQueryItem callback: QueryModuleResponse
): void => { ): void => {
// This callback should be called after prepareQuery // This callback should be called after prepareQuery
expect(asyncCounter).toBe(2); expect(asyncCounter).toBe(2);
@ -77,7 +77,7 @@ describe('Testing API loadIcons', () => {
expect(params).toEqual(expected); expect(params).toEqual(expected);
// Send data // Send data
item.done({ callback('success', {
prefix, prefix,
icons: { icons: {
icon1: { icon1: {
@ -191,7 +191,7 @@ describe('Testing API loadIcons', () => {
const sendQuery = ( const sendQuery = (
host: string, host: string,
params: IconifyAPIQueryParams, params: IconifyAPIQueryParams,
item: PendingQueryItem callback: QueryModuleResponse
): void => { ): void => {
expect(params.type).toBe('icons'); expect(params.type).toBe('icons');
@ -206,7 +206,7 @@ describe('Testing API loadIcons', () => {
expect(params).toEqual(expected); expect(params).toEqual(expected);
// Send data // Send data
item.done({ callback('success', {
prefix, prefix,
icons: { icons: {
icon1: { icon1: {
@ -265,7 +265,7 @@ describe('Testing API loadIcons', () => {
const sendQuery = ( const sendQuery = (
host: string, host: string,
params: IconifyAPIQueryParams, params: IconifyAPIQueryParams,
item: PendingQueryItem callback: QueryModuleResponse
): void => { ): void => {
expect(params.type).toBe('icons'); expect(params.type).toBe('icons');
@ -280,7 +280,7 @@ describe('Testing API loadIcons', () => {
expect(params).toEqual(expected); expect(params).toEqual(expected);
// Send data // Send data
item.done({ callback('success', {
prefix, prefix,
icons: { icons: {
icon1: { icon1: {
@ -351,7 +351,7 @@ describe('Testing API loadIcons', () => {
const sendQuery = ( const sendQuery = (
host: string, host: string,
params: IconifyAPIQueryParams, params: IconifyAPIQueryParams,
item: PendingQueryItem callback: QueryModuleResponse
): void => { ): void => {
// Test input // Test input
expect(host).toBe('https://api1.local'); expect(host).toBe('https://api1.local');
@ -378,7 +378,7 @@ describe('Testing API loadIcons', () => {
body: '<path d="" />', body: '<path d="" />',
}; };
}); });
item.done({ callback('success', {
prefix, prefix,
icons, icons,
// Test mismatched provider: should be ignored because provider name is not affected by actual API response // Test mismatched provider: should be ignored because provider name is not affected by actual API response
@ -452,7 +452,7 @@ describe('Testing API loadIcons', () => {
const sendQuery = ( const sendQuery = (
host: string, host: string,
params: IconifyAPIQueryParams, params: IconifyAPIQueryParams,
item: PendingQueryItem callback: QueryModuleResponse
): void => { ): void => {
queryCounter++; queryCounter++;
params; params;
@ -469,7 +469,7 @@ describe('Testing API loadIcons', () => {
expect(host).toBe('https://api2.local'); expect(host).toBe('https://api2.local');
// Return result // Return result
item.done({ callback('success', {
prefix, prefix,
icons: { icons: {
icon1: { icon1: {
@ -556,7 +556,7 @@ describe('Testing API loadIcons', () => {
const sendQuery = ( const sendQuery = (
host: string, host: string,
params: IconifyAPIQueryParams, params: IconifyAPIQueryParams,
item: PendingQueryItem callback: QueryModuleResponse
): void => { ): void => {
queryCounter++; queryCounter++;
@ -580,7 +580,7 @@ describe('Testing API loadIcons', () => {
expect(host).toBe('https://api2.local'); expect(host).toBe('https://api2.local');
// Return result // Return result
item.done({ callback('success', {
prefix, prefix,
icons: { icons: {
icon1: { icon1: {
@ -599,7 +599,7 @@ describe('Testing API loadIcons', () => {
expect(host).toBe('https://api2.local'); expect(host).toBe('https://api2.local');
// Return result // Return result
item.done({ callback('success', {
prefix, prefix,
icons: { icons: {
icon3: { icon3: {
@ -719,7 +719,7 @@ describe('Testing API loadIcons', () => {
const sendQuery = ( const sendQuery = (
host: string, host: string,
params: IconifyAPIQueryParams, params: IconifyAPIQueryParams,
item: PendingQueryItem callback: QueryModuleResponse
): void => { ): void => {
queryCounter++; queryCounter++;
@ -745,7 +745,7 @@ describe('Testing API loadIcons', () => {
expect(host).toBe('https://api2.local'); expect(host).toBe('https://api2.local');
// Return result // Return result
item.done({ callback('success', {
prefix: params.prefix, prefix: params.prefix,
icons: { icons: {
icon1: { icon1: {
@ -765,7 +765,7 @@ describe('Testing API loadIcons', () => {
expect(host).toBe('https://api2.local'); expect(host).toBe('https://api2.local');
// Return result // Return result
item.done({ callback('success', {
prefix: params.prefix, prefix: params.prefix,
icons: { icons: {
icon2: { icon2: {

View File

@ -353,4 +353,47 @@ describe('Testing mock API module', () => {
isSync = false; isSync = false;
}); });
it('not_found response', (done) => {
const prefix = nextPrefix();
mockAPIData({
type: 'icons',
provider,
prefix,
icons: ['test1', 'test2'],
response: {
prefix,
icons: {},
not_found: ['test1', 'test2'],
},
});
let isSync = true;
loadIcons(
[
{
provider,
prefix,
name: 'test1',
},
],
(loaded, missing, pending) => {
expect(isSync).toBe(false);
expect(loaded).toEqual([]);
expect(pending).toEqual([]);
expect(missing).toEqual([
{
provider,
prefix,
name: 'test1',
},
]);
done();
}
);
isSync = false;
});
}); });

View File

@ -173,8 +173,17 @@ export function validateIconSet(
} }
}); });
// Check not_found
if (data.not_found !== void 0 && !(data.not_found instanceof Array)) {
if (fix) {
delete data.not_found;
} else {
throw new Error('Invalid not_found list');
}
}
// Make sure icons list is not empty // Make sure icons list is not empty
if (!Object.keys(data.icons).length) { if (!Object.keys(data.icons).length && !data.not_found?.length) {
throw new Error('Icon set is empty'); throw new Error('Icon set is empty');
} }