UNPKG

@qiniu/miku-delivery-mp-ks

Version:

Kuaishou Mini Program SDK for Miku Delivery

545 lines (544 loc) 28.9 kB
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); }); });