@qiniu/miku-delivery-mp-ks
Version:
Kuaishou Mini Program SDK for Miku Delivery
545 lines (544 loc) • 28.9 kB
JavaScript
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
jest.setTimeout(30000);
import { delay, delayValue } from '../test';
import Context from '../utils/context';
import { makeMockLogger } from '../log/test';
import CDNResolver, { doWithElt, makeConsistentHash } from '.';
import { makeMockResolveResult, makeMockElt, makeMockGroup, jobSucc, jobFail, makeMockGroups } from './test';
import { LocalDNSError, ResolveError } from './common';
import ConsistentHash from '../utils/consistent-hash';
import { TimeoutError, uuid } from '../utils';
beforeEach(() => {
// 避免 case 间存储互相影响
wx.clearStorageSync();
});
const ctx = new Context();
const appInfo = {
appID: 'foo',
appSalt: 'bar'
};
const clientID = uuid();
const defaultCacheKeyConfig = {
rewrites: [],
queryString: { type: 'all' }
};
describe('CDNResolver', () => {
it('should construct well', () => {
new CDNResolver(makeMockLogger(), appInfo, clientID);
});
function makeMockResolver(dnsResolver) {
return new CDNResolver(makeMockLogger(), appInfo, clientID, dnsResolver);
}
describe('resolveDomain', () => {
it('should work well', () => __awaiter(void 0, void 0, void 0, function* () {
const originalResult = makeMockResolveResult({ groupNum: 1 });
const dnsResolver = jest.fn(() => originalResult);
const resolver = makeMockResolver(dnsResolver);
const result = yield resolver.resolveDomain(ctx, 'foo.com');
expect(dnsResolver).toBeCalledTimes(1);
expect(dnsResolver.mock.calls[0][1]).toBe('foo.com');
expect(result).toMatchObject(originalResult);
}));
it('should work well with ttl', () => __awaiter(void 0, void 0, void 0, function* () {
const originalResultsForFoo = [
makeMockResolveResult({ groupNum: 1, ttl: 1 }),
makeMockResolveResult({ groupNum: 1, ttl: 1 })
];
const originalResultsForBar = [
makeMockResolveResult({ groupNum: 1, ttl: 1 }),
makeMockResolveResult({ groupNum: 1, ttl: 1 })
];
let indexes = { foo: 0, bar: 0 };
const dnsResolver = jest.fn((_, domain) => {
if (domain === 'foo.com')
return originalResultsForFoo[indexes.foo++];
if (domain === 'bar.com')
return originalResultsForBar[indexes.bar++];
throw new Error('Unexpected domain: ' + domain);
});
const resolver = makeMockResolver(dnsResolver);
// 并发 resolve,合并重复的请求
const [result1_1, result1_2, result1_3] = yield Promise.all([
resolver.resolveDomain(ctx, 'foo.com'),
resolver.resolveDomain(ctx, 'foo.com'),
resolver.resolveDomain(ctx, 'bar.com')
]);
expect(dnsResolver).toBeCalledTimes(2);
expect(result1_1).toMatchObject(originalResultsForFoo[0]);
expect(result1_2).toMatchObject(originalResultsForFoo[0]);
expect(result1_3).toMatchObject(originalResultsForBar[0]);
// 缓存未过期,不用重新发请求
const [result2_1, result2_2, result2_3] = yield Promise.all([
resolver.resolveDomain(ctx, 'foo.com'),
resolver.resolveDomain(ctx, 'foo.com'),
resolver.resolveDomain(ctx, 'bar.com')
]);
expect(dnsResolver).toBeCalledTimes(2);
expect(result2_1).toMatchObject(originalResultsForFoo[0]);
expect(result2_2).toMatchObject(originalResultsForFoo[0]);
expect(result2_3).toMatchObject(originalResultsForBar[0]);
yield delay(1500);
// 缓存过期后再 resolve foo.com,使用缓存但触发异步更新
const [result3_1, result3_2, result3_3] = yield Promise.all([
resolver.resolveDomain(ctx, 'foo.com'),
resolver.resolveDomain(ctx, 'foo.com'),
resolver.resolveDomain(ctx, 'foo.com')
]);
expect(dnsResolver).toBeCalledTimes(3);
expect(result3_1).toMatchObject(originalResultsForFoo[0]);
expect(result3_2).toMatchObject(originalResultsForFoo[0]);
expect(result3_3).toMatchObject(originalResultsForFoo[0]);
yield delay(500);
// 再次 resolve foo.com,此时异步更新已完成
expect(dnsResolver).toBeCalledTimes(3);
const result4 = yield resolver.resolveDomain(ctx, 'foo.com');
expect(result4).toMatchObject(originalResultsForFoo[1]);
// 再次 resolve bar.com,使用缓存但触发异步更新
const result5 = yield resolver.resolveDomain(ctx, 'bar.com');
expect(dnsResolver).toBeCalledTimes(4);
expect(result5).toMatchObject(originalResultsForBar[0]);
}));
it('should work well with NotSupportedAreaResolveResult', () => __awaiter(void 0, void 0, void 0, function* () {
const originalResult = { reason: 'NotSupportedArea', ttl: 1 };
const dnsResolver = jest.fn(() => originalResult);
const resolver = makeMockResolver(dnsResolver);
expect(yield resolver.resolveDomain(ctx, 'foo.com')).toMatchObject(originalResult);
expect(dnsResolver).toBeCalledTimes(1);
expect(yield resolver.resolveDomain(ctx, 'foo.com')).toMatchObject(originalResult);
expect(dnsResolver).toBeCalledTimes(1);
yield delay(1000);
expect(yield resolver.resolveDomain(ctx, 'foo.com')).toMatchObject(originalResult);
expect(dnsResolver).toBeCalledTimes(2);
}));
it('should work well with resolveTimeout', () => __awaiter(void 0, void 0, void 0, function* () {
const originalResult = makeMockResolveResult({ groupNum: 1 });
const dnsResolver = jest.fn(() => delayValue(originalResult, 1000));
const resolver = new CDNResolver(makeMockLogger(), appInfo, clientID, dnsResolver, 500);
const err = yield resolver.resolveDomain(ctx, 'foo.com').then(() => null, e => e);
expect(err).toBeInstanceOf(ResolveError);
expect(err.cause).toBeInstanceOf(TimeoutError);
}));
});
describe('do', () => {
const url = 'https://foo.com/bar';
it('should work well when doWithGroup succeeded', () => __awaiter(void 0, void 0, void 0, function* () {
const result = makeMockResolveResult({ groupNum: 1, eltNum: 1, addrNum: 1 });
const dnsResolver = jest.fn(() => result);
const resolver = makeMockResolver(dnsResolver);
const job = jest.fn(() => undefined);
yield resolver.do(ctx, url, 2, job);
expect(dnsResolver.mock.calls[0][1]).toBe('foo.com');
expect(job).toBeCalledWith(result.groups[0].elts[0].addrs[0]);
}));
it('should throw when empty groups', () => __awaiter(void 0, void 0, void 0, function* () {
const result = makeMockResolveResult({ groupNum: 0 });
const resolver = makeMockResolver(() => result);
yield expect(resolver.do(ctx, url, 2, () => undefined)).rejects.toThrow();
}));
it('should retry with differnt group when doWithGroup failed', () => __awaiter(void 0, void 0, void 0, function* () {
const result = makeMockResolveResult({ groupNum: 2, eltNum: 1, addrNum: 1 });
const job = jest.fn().mockReturnValueOnce(jobFail()).mockReturnValue(jobSucc());
const resolver = makeMockResolver(() => result);
yield resolver.do(ctx, url, 2, job);
expect(job).toBeCalledTimes(2);
expect(job).toBeCalledWith(result.groups[0].elts[0].addrs[0]);
expect(job).toBeCalledWith(result.groups[1].elts[0].addrs[0]);
}));
describe('should retry $attempts times at most', () => {
function withAttempts(attempts) {
return __awaiter(this, void 0, void 0, function* () {
const result = makeMockResolveResult({ groupNum: attempts + 1, eltNum: 1, addrNum: 1 });
const resolver = makeMockResolver(() => result);
const calledIPs = [];
const job = jest.fn((resolved) => {
calledIPs.push(resolved.ip);
return jobFail();
});
yield expect(resolver.do(ctx, url, attempts, job)).rejects.toThrow();
expect(job).toBeCalledTimes(attempts);
expect(new Set(calledIPs).size).toBe(attempts);
});
}
it('attempts: 2', () => withAttempts(2));
it('attempts: 4', () => withAttempts(4));
});
it('should use same addr if url not changed', () => __awaiter(void 0, void 0, void 0, function* () {
const originalResult = makeMockResolveResult({ groupNum: 2, eltNum: 3, addrNum: 4, ttl: 1 });
const dnsResolver = jest.fn(() => originalResult);
const resolver = makeMockResolver(dnsResolver);
const calledIPs = [];
const job = jest.fn((resolved) => {
if (!calledIPs.includes(resolved.ip))
calledIPs.push(resolved.ip);
return jobSucc();
});
for (let i = 0; i < 10; i++) {
yield resolver.do(ctx, url, 1, job);
}
expect(calledIPs.length).toBe(1);
}));
it('should use random group based on url', () => __awaiter(void 0, void 0, void 0, function* () {
const originalResult = makeMockResolveResult({ groupNum: 2, eltNum: 1, addrNum: 1, ttl: 1 });
const dnsResolver = jest.fn(() => originalResult);
const resolver = makeMockResolver(dnsResolver);
const calledIPs = [];
const job = jest.fn((resolved) => {
if (!calledIPs.includes(resolved.ip))
calledIPs.push(resolved.ip);
return jobSucc();
});
for (let i = 0; i < 100; i++) {
yield resolver.do(ctx, `https://foo.com/bar${i}.jpg`, 1, job);
}
expect(calledIPs.length).toBe(2);
}));
it('should match rule correctly', () => __awaiter(void 0, void 0, void 0, function* () {
const groups = makeMockGroups({ num: 3, eltNum: 1, addrNum: 1 });
const originalResult = {
bucket: '',
groups,
rules: [
{ pattern: '\\.jpe?g$', servicerate: 1, groups: [{ groupidx: 0, weight: 100, splitn: 1 }] },
{ pattern: '\\.png$', servicerate: 1, groups: [{ groupidx: 1, weight: 100, splitn: 1 }] },
{ pattern: '\\.\\w+$', servicerate: 1, groups: [{ groupidx: 2, weight: 100, splitn: 1 }] },
],
cacheKey: defaultCacheKeyConfig,
mediaOptimization: { threshold: 0 },
ttl: 10
};
const dnsResolver = jest.fn(() => originalResult);
const resolver = makeMockResolver(dnsResolver);
const job = jest.fn(jobSucc);
yield resolver.do(ctx, 'https://foo.com/bar.jpg', 1, job);
expect(job).toBeCalledTimes(1);
expect(job).toBeCalledWith(groups[0].elts[0].addrs[0]);
job.mockReset();
yield resolver.do(ctx, 'https://foo.com/bar.jpeg', 1, job);
expect(job).toBeCalledTimes(1);
expect(job).toBeCalledWith(groups[0].elts[0].addrs[0]);
job.mockReset();
yield resolver.do(ctx, 'https://foo.com/bar.png', 1, job);
expect(job).toBeCalledTimes(1);
expect(job).toBeCalledWith(groups[1].elts[0].addrs[0]);
job.mockReset();
yield resolver.do(ctx, 'https://foo.com/bar.mp4', 1, job);
expect(job).toBeCalledTimes(1);
expect(job).toBeCalledWith(groups[2].elts[0].addrs[0]);
expect(resolver.do(ctx, 'https://foo.com/bar', 1, job)).rejects.toBeInstanceOf(LocalDNSError);
}));
it('should respect servicerate', () => __awaiter(void 0, void 0, void 0, function* () {
const groups = makeMockGroups({ num: 2, eltNum: 1, addrNum: 1 });
const originalResult = {
bucket: '',
groups,
rules: [
{ pattern: '\\.jpe?g$', servicerate: 0.5, groups: [{ groupidx: 0, weight: 100, splitn: 1 }] },
{ pattern: '.', servicerate: 1, groups: [{ groupidx: 1, weight: 100, splitn: 1 }] },
],
cacheKey: defaultCacheKeyConfig,
mediaOptimization: { threshold: 0 },
ttl: 10
};
const dnsResolver = jest.fn(() => originalResult);
const resolver = makeMockResolver(dnsResolver);
const job = jest.fn(jobSucc);
{
let localDNSErrors = [];
for (let i = 0; i < 100; i++) {
try {
yield resolver.do(ctx, 'https://foo.com/bar.jpg', 1, job);
}
catch (e) {
if (e instanceof LocalDNSError)
localDNSErrors.push(e);
}
}
expect(job.mock.calls.length + localDNSErrors.length).toBe(100);
expect(job).toBeCalledWith(groups[0].elts[0].addrs[0]);
expect(job).not.toBeCalledWith(groups[1].elts[0].addrs[0]);
expect(localDNSErrors.length).toBeGreaterThan(0);
}
{
job.mockReset();
let localDNSErrors = [];
for (let i = 0; i < 100; i++) {
try {
yield resolver.do(ctx, 'https://foo.com/bar.png', 1, job);
}
catch (e) {
if (e instanceof LocalDNSError)
localDNSErrors.push(e);
}
}
expect(localDNSErrors).toHaveLength(0);
expect(job).toBeCalledTimes(100);
expect(job).toBeCalledWith(groups[1].elts[0].addrs[0]);
expect(job).not.toBeCalledWith(groups[0].elts[0].addrs[0]);
}
}));
it('should use cache key for hash', () => __awaiter(void 0, void 0, void 0, function* () {
const groups = makeMockGroups({ num: 2, eltNum: 2, addrNum: 1 });
const originalResult = {
bucket: '',
groups,
rules: [
{ pattern: '.', servicerate: 1, groups: [{ groupidx: 0, weight: 100, splitn: 1 }] }
],
cacheKey: {
rewrites: [],
queryString: { type: 'none' }
},
mediaOptimization: { threshold: 0 },
ttl: 60
};
const dnsResolver = jest.fn(() => originalResult);
const resolver = new CDNResolver(makeMockLogger(), appInfo, clientID, dnsResolver);
const resultHosts = [];
const job = jest.fn((resolved) => {
resultHosts.push(resolved.host);
return jobSucc();
});
for (let i = 0; i < 100; i++) {
yield resolver.do(ctx, `https://foo.com/bar.jpg?${i}`, 1, job);
}
expect(resultHosts.every(r => r === resultHosts[0])).toBe(true); // 所有的 result 都是相同的,因为去 query 后 URL 相同,hash 结果相同
}));
it('should work well with NotSupportedAreaResolveResult', () => __awaiter(void 0, void 0, void 0, function* () {
const originalResult = { reason: 'NotSupportedArea', ttl: 1 };
const dnsResolver = jest.fn(() => originalResult);
const resolver = makeMockResolver(dnsResolver);
yield expect(resolver.do(ctx, 'https://foo.com/a.mp4', 1, jobSucc)).rejects.toBeInstanceOf(LocalDNSError);
expect(dnsResolver).toBeCalledTimes(1);
yield expect(resolver.do(ctx, 'https://foo.com/a.mp4', 1, jobSucc)).rejects.toBeInstanceOf(LocalDNSError);
expect(dnsResolver).toBeCalledTimes(1);
yield delay(1000);
yield expect(resolver.do(ctx, 'https://foo.com/a.mp4', 1, jobSucc)).rejects.toBeInstanceOf(LocalDNSError);
expect(dnsResolver).toBeCalledTimes(2);
}));
it('should work well with resolveTimeout', () => __awaiter(void 0, void 0, void 0, function* () {
const originalResult = makeMockResolveResult({ groupNum: 1 });
const dnsResolver = jest.fn(() => delayValue(originalResult, 1000));
const resolver = new CDNResolver(makeMockLogger(), appInfo, clientID, dnsResolver, 500);
const err = yield resolver.do(ctx, 'https://foo.com/a.mp4', 1, jobSucc).then(() => null, e => e);
expect(err).toBeInstanceOf(ResolveError);
expect(err.cause).toBeInstanceOf(TimeoutError);
}));
it('should not retry with failed elt for specific duration', () => __awaiter(void 0, void 0, void 0, function* () {
const result = makeMockResolveResult({ groupNum: 2, eltNum: 2, addrNum: 2 });
const resolver = makeMockResolver(() => result);
const failedElt = result.groups[0].elts[1];
const failedHosts = failedElt.addrs.map(addr => addr.host);
// 先尝试 100 次,尽量确保 failedElt 被尝试过并被失败禁用 5s
// TODO: 实际测试发现即使尝试 100 次也有很大概率不会命中 failedElt,后续看下选择算法是不是不够随机
let marked = false;
const job1 = jest.fn((resolved) => {
if (failedHosts.includes(resolved.host)) {
marked = true;
return jobFail();
}
return jobSucc();
});
for (let i = 0; i < 100; i++) {
yield resolver.do(ctx, url, 2, job1);
}
// 如果未命中 failedElt,后面的步骤肯定跑不过,直接跳过先
if (!marked) {
console.log('skip test');
return;
}
const job2 = jest.fn(jobSucc);
for (let i = 0; i < 50; i++) {
yield resolver.do(ctx, url, 2, job2);
}
expect(job2).not.toBeCalledWith(failedElt.addrs[0]);
expect(job2).not.toBeCalledWith(failedElt.addrs[1]);
// 5.5s 后解禁
yield delay(5500);
const job3 = jest.fn((resolved) => failedHosts.includes(resolved.host) ? jobFail() : jobSucc());
for (let i = 0; i < 100; i++) {
yield resolver.do(ctx, url, 2, job3);
}
// 这里有一定概率会挂,重跑试试..
expect(job3).toBeCalledWith(failedElt.addrs[0]);
expect(job3).toBeCalledWith(failedElt.addrs[1]);
// 再次禁用 10s,等 5.5s 后仍禁用
yield delay(5500);
const job4 = jest.fn(jobSucc);
for (let i = 0; i < 50; i++) {
yield resolver.do(ctx, url, 2, job4);
}
expect(job4).not.toBeCalledWith(failedElt.addrs[0]);
expect(job4).not.toBeCalledWith(failedElt.addrs[1]);
// 再等 5.5s 后不再禁用
yield delay(5500);
const job5 = jest.fn(jobSucc);
for (let i = 0; i < 100; i++) {
yield resolver.do(ctx, url, 2, job5);
}
expect(job5).toBeCalledWith(failedElt.addrs[0]);
expect(job5).toBeCalledWith(failedElt.addrs[1]);
// 成功后会清除历史禁用信息,下次失败禁用 5s
for (let i = 0; i < 100; i++) {
yield resolver.do(ctx, url, 2, job1);
}
const job6 = jest.fn(jobSucc);
for (let i = 0; i < 50; i++) {
yield resolver.do(ctx, url, 2, job6);
}
expect(job6).not.toBeCalledWith(failedElt.addrs[0]);
expect(job6).not.toBeCalledWith(failedElt.addrs[1]);
// 5.5s 后解禁
yield delay(5500);
const job7 = jest.fn(jobSucc);
for (let i = 0; i < 100; i++) {
yield resolver.do(ctx, url, 2, job7);
}
expect(job7).toBeCalledWith(failedElt.addrs[0]);
expect(job7).toBeCalledWith(failedElt.addrs[1]);
}));
});
describe('doWithGroup', () => {
const url = 'https://foo.com/bar';
it('should work well when doWithElt succeeded', () => __awaiter(void 0, void 0, void 0, function* () {
const result = makeMockResolveResult({ groupNum: 1, eltNum: 1, addrNum: 1 });
const resolver = makeMockResolver(() => result);
const job = jest.fn(() => undefined);
yield resolver.doWithGroup(new Context(), result.groups[0], url, 1, job);
expect(job).toBeCalledWith(result.groups[0].elts[0].addrs[0]);
}));
it('should throw when empty elts', () => __awaiter(void 0, void 0, void 0, function* () {
const result = makeMockResolveResult({ groupNum: 1, eltNum: 0 });
const resolver = makeMockResolver(() => result);
yield expect(resolver.doWithGroup(new Context(), result.groups[0], url, 1, () => undefined)).rejects.toThrow();
}));
it('should retry with different elt when doWithElt failed', () => __awaiter(void 0, void 0, void 0, function* () {
const result = makeMockResolveResult({ groupNum: 1, eltNum: 2, addrNum: 1 });
const resolver = makeMockResolver(() => result);
const job = jest.fn().mockReturnValueOnce(jobFail()).mockReturnValue(jobSucc());
yield resolver.doWithGroup(new Context(), result.groups[0], url, 2, job);
expect(job).toBeCalledTimes(2);
expect(job).toBeCalledWith(result.groups[0].elts[0].addrs[0]);
expect(job).toBeCalledWith(result.groups[0].elts[1].addrs[0]);
}));
it('should retry 2 times at most', () => __awaiter(void 0, void 0, void 0, function* () {
const result = makeMockResolveResult({ groupNum: 1, eltNum: 3, addrNum: 1 });
const resolver = makeMockResolver(() => result);
const calledIPs = [];
const job = jest.fn((resolved) => {
calledIPs.push(resolved.ip);
return jobFail();
});
yield expect(resolver.doWithGroup(new Context(), result.groups[0], url, 3, job)).rejects.toThrow();
expect(job).toBeCalledTimes(2);
expect(new Set(calledIPs).size).toBe(2);
}));
it('should not retry with not enough elts', () => __awaiter(void 0, void 0, void 0, function* () {
const result = makeMockResolveResult({ groupNum: 1, eltNum: 1, addrNum: 1 });
const resolver = makeMockResolver(() => result);
const job = jest.fn().mockReturnValue(jobFail());
yield expect(resolver.doWithGroup(new Context(), result.groups[0], url, 2, job)).rejects.toThrow();
expect(job).toBeCalledTimes(1);
}));
it('should use different elts based on url', () => __awaiter(void 0, void 0, void 0, function* () {
const result = makeMockResolveResult({ groupNum: 1, eltNum: 3, addrNum: 1, eltReplicasBase: 100 });
const resolver = makeMockResolver(() => result);
const elts = result.groups[0].elts;
let nums = [0, 0, 0];
for (let i = 0; i < 100; i++) {
const randomUrl = `https://foo${i}.com`;
yield resolver.doWithGroup(new Context(), result.groups[0], randomUrl, 1, (resolved) => {
const eltIdx = elts.findIndex(e => e.addrs[0] === resolved);
nums[eltIdx]++;
return jobSucc();
});
}
expect(nums[0] + nums[1] + nums[2]).toBe(100);
expect(nums[0]).toBeGreaterThan(0);
expect(nums[1]).toBeGreaterThan(0);
expect(nums[2]).toBeGreaterThan(0);
}));
it('should use random elts within splitn', () => __awaiter(void 0, void 0, void 0, function* () {
const result = makeMockResolveResult({ groupNum: 1, eltNum: 3, addrNum: 1, eltReplicasBase: 100 });
const elts = result.groups[0].elts;
const url = `https://foo.com`;
const splitn = 2;
let nums = [0, 0, 0];
for (let i = 0; i < 100; i++) {
let id = uuid();
let resolver = new CDNResolver(makeMockLogger(), appInfo, id, () => result);
yield resolver.doWithGroup(new Context(), result.groups[0], url, splitn, (resolved) => {
const eltIdx = elts.findIndex(e => e.addrs[0] === resolved);
nums[eltIdx]++;
return jobSucc();
});
}
expect(nums[0] + nums[1] + nums[2]).toBe(100);
expect(nums.filter(n => n > 0).length).toBe(splitn);
}));
});
});
describe('doWithElt', () => {
const url = 'https://foo.com/bar';
it('should work well when job succeeded', () => __awaiter(void 0, void 0, void 0, function* () {
const elt = makeMockElt({ addrNum: 1 });
const job = jest.fn(() => undefined);
yield doWithElt(elt, clientID, url, job);
expect(job).toBeCalledWith(elt.addrs[0]);
}));
it('should throw when empty ips', () => __awaiter(void 0, void 0, void 0, function* () {
const elt = makeMockElt({ addrNum: 0 });
yield expect(doWithElt(elt, clientID, url, () => undefined)).rejects.toThrow();
}));
it('should retry with differnt ip when job failed', () => __awaiter(void 0, void 0, void 0, function* () {
const elt = makeMockElt({ addrNum: 2 });
const job = jest.fn().mockReturnValueOnce(jobFail()).mockReturnValue(jobSucc());
yield doWithElt(elt, clientID, url, job);
expect(job).toBeCalledTimes(2);
expect(job).toBeCalledWith(elt.addrs[0]);
expect(job).toBeCalledWith(elt.addrs[1]);
}));
it('should retry 2 times at most', () => __awaiter(void 0, void 0, void 0, function* () {
const elt = makeMockElt({ addrNum: 3 });
const calledIPs = [];
const job = jest.fn((resolved) => {
calledIPs.push(resolved.ip);
return jobFail();
});
yield expect(doWithElt(elt, clientID, url, job)).rejects.toThrow();
expect(job).toBeCalledTimes(2);
expect(new Set(calledIPs).size).toBe(2);
}));
it('should not retry with not enough ips', () => __awaiter(void 0, void 0, void 0, function* () {
const elt = makeMockElt({ addrNum: 1 });
const job = jest.fn().mockReturnValue(jobFail());
yield expect(doWithElt(elt, clientID, url, job)).rejects.toThrow();
expect(job).toBeCalledTimes(1);
}));
});
describe('makeConsistentHash', () => {
it('should work well', () => {
const group = makeMockGroup({ eltNum: 10, addrNum: 1 });
const ch = makeConsistentHash(group);
expect(ch instanceof ConsistentHash);
expect(ch['members'].size).toBe(10);
});
it('should work well with cache', () => {
const group1 = makeMockGroup({ eltNum: 10, addrNum: 1 });
const group2 = makeMockGroup({ eltNum: 5, addrNum: 1 });
const ch1 = makeConsistentHash(group1);
const ch2 = makeConsistentHash(group1);
const ch3 = makeConsistentHash(group2);
expect(ch2).toBe(ch1);
expect(ch1['members'].size).toBe(10);
expect(ch3).not.toBe(ch1);
expect(ch3['members'].size).toBe(5);
});
});