{"version":3,"sources":["common/spec/mutexHelperSpec.ts"],"names":[],"mappings":"","file":"../../../common/spec/mutexHelperSpec.d.ts","sourcesContent":["import {\n  backoff, backoffIterator, Barrier, deepCopy, Mutex, once, sleep, squish,\n} from '../mutexHelper.js';\n\ndescribe('the littleware.mutexHelper', () => {\n  it('can sleep for a few seconds', (done) => {\n    const startMs = Date.now();\n    sleep(3000).then(\n      () => {\n        const endMs = Date.now();\n        expect(endMs - startMs).toBeGreaterThan(2900);\n        done();\n      },\n    );\n  });\n\n  it('can squish a lambda to avoid overlapping calls', (done) => {\n    const lambda = squish(() => sleep(1000));\n    const s1 = lambda();\n    const s2 = lambda();\n    expect(s1).toBe(s2);\n    s2.then(() => {\n      const s3 = lambda();\n      expect(s3).not.toBe(s2);\n      done();\n    });\n  });\n\n  it('can setup a backoff iterator', () => {\n    const numRetries = 3;\n    const backoffMs = 100;\n    const it = backoffIterator(numRetries, backoffMs);\n    let count = 0;\n    let lastValue = 0;\n    for (const value of it) {\n      if (count === 1) { // first retry\n        expect(value).toBeGreaterThan(backoffMs / 2);\n        expect(value).toBeLessThan((backoffMs * 3) / 2);\n      } else {\n        expect(value).toBe(lastValue + lastValue);\n      }\n      lastValue = value;\n      expect(count).toBeLessThan(numRetries + 1);\n      count += 1;\n    }\n    expect(count).toBe(numRetries + 1);\n    for (let i = 0; i < 2; i++) {\n      const last = it.next();\n      expect(last.done).toBe(true);\n      expect(last.value).toBe(lastValue + lastValue);\n    }\n  });\n\n  it('can backoff and retry to failure', (done) => {\n    const maxRetries = 3;\n    const backoffMs = 100;\n    let startMs = Date.now();\n    let lastRunMs = 0;\n    let count = 0;\n\n    const lambda = () => {\n      const nowMs = Date.now();\n      if (lastRunMs) {\n        expect(nowMs - lastRunMs).toBeGreaterThan((count * backoffMs) / 2);\n      }\n      expect(count).toBeLessThan(maxRetries + 2);\n      count += 1;\n      lastRunMs = nowMs;\n      return Promise.reject(new Error('always fail'));\n    };\n\n    const proxy = backoff(lambda, maxRetries, backoffMs);\n    proxy().then(\n      () => {\n        done.fail('lambda should always fail');\n      },\n    ).catch(\n      () => {\n        expect(count).toBe(maxRetries + 2);\n        expect(Date.now() - startMs).toBeGreaterThan(backoffMs);\n        count = 0;\n        lastRunMs = 0;\n        startMs = Date.now();\n        return proxy();\n      },\n    ).catch(\n      // ensure proxy runs are indempotent\n      () => {\n        expect(count).toBe(maxRetries + 2);\n        expect(Date.now() - startMs).toBeGreaterThan(backoffMs);\n        done();\n      },\n    );\n  });\n\n  it('can backoff and retry to success', (done) => {\n    const maxRetries = 3;\n    const backoffMs = 100;\n    let lastRunMs = 0;\n    let count = 0;\n\n    const lambda = (message) => {\n      const nowMs = Date.now();\n      if (lastRunMs) {\n        expect(nowMs - lastRunMs).toBeGreaterThan((count * backoffMs) / 2);\n      }\n      expect(count).toBeLessThan(maxRetries + 2);\n      count += 1;\n      lastRunMs = nowMs;\n      if (count < 2) {\n        return Promise.reject(new Error(`fail on ${count}`));\n      }\n      return Promise.resolve(`${message} on ${count}`);\n    };\n\n    const proxy = backoff(lambda, maxRetries, backoffMs);\n    proxy('success').then(\n      (str) => {\n        expect(str).toBe('success on 2');\n        count = 0;\n        lastRunMs = 0;\n        return proxy('success');\n      },\n    ).then(\n      // ensure proxy invocations are indempotent\n      (str) => {\n        expect(str).toBe('success on 2');\n        done();\n      },\n    ).catch(\n      () => {\n        done.fail('backoff should succeed on 2nd retry');\n      },\n    );\n  });\n\n  it('can setup a one-call cache-proxy', () => {\n    const count = (() => {\n      let counter = 0;\n      return () => counter++;\n    })();\n    const cacheCount = once(count);\n    expect(cacheCount()).toBe(0);\n    expect(cacheCount()).toBe(0);\n    expect(count()).toBe(1);\n    expect(cacheCount()).toBe(0);\n  });\n\n  /* eslint-disable @typescript-eslint/no-loop-func */\n  it('can rate limit access to a critical section', (done) => {\n    const mx = new Mutex();\n    let counter = 0;\n    const batch = [];\n    for (let i = 0; i < mx.maxQueueLen + mx.maxConcurrency; ++i) {\n      const num = i;\n      const task = mx.enter(() => sleep(100).then(\n        () => {\n          counter += 1;\n          return counter;\n        },\n      )).then(\n        (numPlus1) => {\n          expect(numPlus1).toBe(num + 1);\n        },\n      );\n      batch.push(task);\n    }\n    mx.enter(() => Promise.resolve('ok')).then(\n      () => { done.fail('should have been throttled'); },\n      (err) => { expect(err).toEqual(new Error('mutex throttle')); },\n    ).then(\n      () => Promise.all(batch),\n    ).then(\n      () => done(),\n    );\n  });\n\n  it('can do a deep copy', () => {\n    const testObj = {\n      abc: {\n        def: {\n          g: 123,\n          hij: [{ xyz: 123 }],\n        },\n      },\n      whatever: 'whatever',\n    };\n    const copy = deepCopy(testObj, true);\n    expect(copy).not.toBe(testObj, 'deepCopy creates new object');\n    expect(copy.abc).not.toBe(testObj.abc);\n    expect(copy.abc.def).not.toBe(testObj.abc.def);\n    expect(copy.abc.def.g).toEqual(testObj.abc.def.g);\n    expect(copy.abc.def.hij[0]).not.toBe(testObj.abc.def.hij[0]);\n    expect(copy.abc.def.hij[0].xyz).toEqual(testObj.abc.def.hij[0].xyz);\n\n    try {\n      copy.whatever = 'freeze';\n      fail('assigment to frozen object should fail');\n    } catch (ex) {\n      expect(ex).toBeDefined();\n    }\n    expect(copy.whatever).toEqual(testObj.whatever, 'freeze works');\n  });\n});\n\ndescribe('the little barrier', () => {\n  it('can signal and wait on a barrier', (done) => {\n    const barrier = new Barrier<string>();\n    const value = 'frickjack';\n\n    barrier.wait().then(\n      () => barrier.wait().then((str) => str),\n    ).then(\n      (str) => {\n        expect(str).toEqual(value);\n        done();\n      },\n    );\n    barrier.signal(value);\n  });\n});\n"]}