npm package diff
Package: @forge/cache
Versions: 1.0.3-next.0-experimental-ab129b0-experimental-3bf9516 - 1.0.3-next.0-experimental-47556b0
File: package/out/__test__/cache.test.js
Index: package/out/__test__/cache.test.js
===================================================================
--- package/out/__test__/cache.test.js
+++ package/out/__test__/cache.test.js
@@ -0,0 +1,358 @@
+"use strict";
+Object.defineProperty(exports, "__esModule", { value: true });
+const tslib_1 = require("tslib");
+const node_fetch_1 = tslib_1.__importDefault(require("node-fetch"));
+const cache_1 = require("../cache");
+jest.mock('node-fetch');
+const fetchMock = node_fetch_1.default;
+describe('Forge Cache', () => {
+ beforeEach(() => {
+ global['__forge_runtime__'] = {
+ proxy: {
+ token: 'token',
+ url: 'https://proxy.atlassian.com'
+ },
+ rms: {
+ url: 'https://dev.services.atlassian.com',
+ host: 'rockmelon-storage.dev.atl-paas.net'
+ },
+ tracing: {
+ traceId: 'trace-id-test',
+ spanId: 'span-id-test'
+ }
+ };
+ });
+ it('chooses the v2 fetch based on runtime', async () => {
+ const cacheClient = (0, cache_1.connect)();
+ await cacheClient['client']('asdf');
+ expect(fetchMock).toHaveBeenCalled();
+ });
+});
+describe('createFetch', () => {
+ it('fetch fails when rms config is not available', async () => {
+ global['__forge_runtime__'] = {
+ proxy: {
+ token: 'token',
+ url: 'https://proxy.atlassian.com'
+ }
+ };
+ await expect((0, cache_1.createFetchRmsRuntimeV2)()).rejects.toThrowError(new Error('RMS config not available.'));
+ });
+ it('creates a fetch that adds the right headers and url', async () => {
+ global['__forge_runtime__'] = {
+ proxy: {
+ token: 'token',
+ url: 'https://proxy.atlassian.com'
+ },
+ rms: {
+ url: 'https://dev.services.atlassian.com',
+ host: 'rockmelon-storage.dev.atl-paas.net'
+ },
+ tracing: {
+ traceId: 'trace-id-test',
+ spanId: 'span-id-test'
+ }
+ };
+ const fetch = (0, cache_1.createFetchRmsRuntimeV2)();
+ await fetch('path');
+ const [absoluteUrl, options] = fetchMock.mock.lastCall ?? ['', {}];
+ expect(absoluteUrl.toString()).toEqual('https://rms/path');
+ expect((options?.agent)['passthrough']['keepAlive']).toBeTruthy();
+ expect(options?.headers).toMatchObject({
+ Authorization: 'Bearer token',
+ Host: 'rockmelon-storage.dev.atl-paas.net',
+ 'x-b3-traceid': 'trace-id-test',
+ 'x-b3-spanid': 'span-id-test'
+ });
+ });
+});
+describe('Cache', () => {
+ function buildCache() {
+ const fetch = jest.fn();
+ const cache = new cache_1.Cache(fetch);
+ return { cache, fetch };
+ }
+ describe('set', () => {
+ it('handles success', async () => {
+ const { cache, fetch } = buildCache();
+ fetch.mockResolvedValueOnce({ ok: true, text: async () => JSON.stringify({}), status: 200 });
+ await cache.set('key', 'value');
+ expect(fetch.mock.lastCall).toMatchSnapshot();
+ });
+ it('handles success with ttl', async () => {
+ const { cache, fetch } = buildCache();
+ fetch.mockResolvedValueOnce({ ok: true, text: async () => JSON.stringify({}), status: 200 });
+ await cache.set('key', 'value', { ttlSeconds: 100 });
+ expect(fetch.mock.lastCall).toMatchSnapshot();
+ });
+ it('handles failure', async () => {
+ const { cache, fetch } = buildCache();
+ fetch.mockResolvedValueOnce({ ok: false, text: async () => 'Not allowed', status: 403 });
+ await expect(cache.set('key', 'value', { ttlSeconds: 100 })).rejects.toMatchError(new cache_1.ApiError(403, 'UNKNOWN_ERROR', 'Not allowed'));
+ expect(fetch.mock.lastCall).toMatchSnapshot();
+ });
+ it('handles failure with error code', async () => {
+ const { cache, fetch } = buildCache();
+ fetch.mockResolvedValueOnce({
+ ok: false,
+ text: async () => JSON.stringify({ error: { code: 'NOT_ALLOWED', title: 'Invalid operation' } }),
+ status: 400
+ });
+ await expect(cache.set('key', 'value', { ttlSeconds: 100 })).rejects.toMatchError(new cache_1.ApiError(400, 'NOT_ALLOWED', '"title":"Invalid operation"'));
+ });
+ });
+ describe('setIfNotExists', () => {
+ it('handles success', async () => {
+ const { cache, fetch } = buildCache();
+ fetch.mockResolvedValueOnce({ ok: true, text: async () => JSON.stringify({ response: 'OK' }), status: 200 });
+ const result = await cache.setIfNotExists('key', 'value');
+ expect(result).toEqual('OK');
+ expect(fetch.mock.lastCall).toMatchSnapshot();
+ });
+ it('handles success when key exists', async () => {
+ const { cache, fetch } = buildCache();
+ fetch.mockResolvedValueOnce({ ok: true, text: async () => JSON.stringify({ response: null }), status: 200 });
+ const result = await cache.setIfNotExists('key', 'value');
+ expect(result).toBeNull();
+ expect(fetch.mock.lastCall).toMatchSnapshot();
+ });
+ it('handles success with ttl', async () => {
+ const { cache, fetch } = buildCache();
+ fetch.mockResolvedValueOnce({ ok: true, text: async () => JSON.stringify({ response: 'OK' }), status: 200 });
+ await cache.setIfNotExists('key', 'value', { ttlSeconds: 100 });
+ expect(fetch.mock.lastCall).toMatchSnapshot();
+ });
+ it('handles failure', async () => {
+ const { cache, fetch } = buildCache();
+ fetch.mockResolvedValueOnce({ ok: false, text: async () => 'Not allowed', status: 403 });
+ await expect(cache.setIfNotExists('key', 'value', { ttlSeconds: 100 })).rejects.toMatchError(new cache_1.ApiError(403, 'UNKNOWN_ERROR', 'Not allowed'));
+ expect(fetch.mock.lastCall).toMatchSnapshot();
+ });
+ });
+ describe('get', () => {
+ it('handles success', async () => {
+ const { cache, fetch } = buildCache();
+ fetch.mockResolvedValueOnce({
+ ok: true,
+ text: async () => JSON.stringify({ response: 'asdfasdf' }),
+ status: 200
+ });
+ const result = await cache.get('key');
+ expect(result).toEqual('asdfasdf');
+ expect(fetch.mock.lastCall).toMatchSnapshot();
+ });
+ it('handles success when key does not exist', async () => {
+ const { cache, fetch } = buildCache();
+ fetch.mockResolvedValueOnce({ ok: true, text: async () => JSON.stringify({ response: null }), status: 200 });
+ const result = await cache.get('key');
+ expect(result).toBeNull();
+ expect(fetch.mock.lastCall).toMatchSnapshot();
+ });
+ it('handles failure', async () => {
+ const { cache, fetch } = buildCache();
+ fetch.mockResolvedValueOnce({ ok: false, text: async () => 'Not allowed', status: 403 });
+ await expect(cache.get('key')).rejects.toMatchError(new cache_1.ApiError(403, 'UNKNOWN_ERROR', 'Not allowed'));
+ expect(fetch.mock.lastCall).toMatchSnapshot();
+ });
+ });
+ describe('getAndSet', () => {
+ it('handles success', async () => {
+ const { cache, fetch } = buildCache();
+ fetch.mockResolvedValueOnce({
+ ok: true,
+ text: async () => JSON.stringify({ response: 'oldValue' }),
+ status: 200
+ });
+ const result = await cache.getAndSet('key', 'newValue');
+ expect(result).toEqual('oldValue');
+ expect(fetch.mock.lastCall).toMatchSnapshot();
+ });
+ it('handles success when key does not exist', async () => {
+ const { cache, fetch } = buildCache();
+ fetch.mockResolvedValueOnce({ ok: true, text: async () => JSON.stringify({ response: null }), status: 200 });
+ const result = await cache.getAndSet('key', 'value');
+ expect(result).toBeNull();
+ expect(fetch.mock.lastCall).toMatchSnapshot();
+ });
+ it('handles success with ttl', async () => {
+ const { cache, fetch } = buildCache();
+ fetch.mockResolvedValueOnce({
+ ok: true,
+ text: async () => JSON.stringify({ response: 'oldValue' }),
+ status: 200
+ });
+ await cache.getAndSet('key', 'newValue', { ttlSeconds: 100 });
+ expect(fetch.mock.lastCall).toMatchSnapshot();
+ });
+ it('handles failure', async () => {
+ const { cache, fetch } = buildCache();
+ fetch.mockResolvedValueOnce({ ok: false, text: async () => 'Not allowed', status: 403 });
+ await expect(cache.getAndSet('key', 'value', { ttlSeconds: 100 })).rejects.toMatchError(new cache_1.ApiError(403, 'UNKNOWN_ERROR', 'Not allowed'));
+ expect(fetch.mock.lastCall).toMatchSnapshot();
+ });
+ });
+ describe('incrementAndGet', () => {
+ it('handles success', async () => {
+ const { cache, fetch } = buildCache();
+ fetch.mockResolvedValueOnce({ ok: true, text: async () => JSON.stringify({ response: 0 }), status: 200 });
+ const result = await cache.incrementAndGet('key');
+ expect(result).toEqual(0);
+ expect(fetch.mock.lastCall).toMatchSnapshot();
+ });
+ it('handles success with ttl', async () => {
+ const { cache, fetch } = buildCache();
+ fetch.mockResolvedValueOnce({ ok: true, text: async () => JSON.stringify({ response: 0 }), status: 200 });
+ const result = await cache.incrementAndGet('key', { ttlSeconds: 100 });
+ expect(result).toEqual(0);
+ expect(fetch.mock.lastCall).toMatchSnapshot();
+ });
+ it('handles success when key exists', async () => {
+ const { cache, fetch } = buildCache();
+ fetch.mockResolvedValueOnce({ ok: true, text: async () => JSON.stringify({ response: 11 }), status: 200 });
+ const result = await cache.incrementAndGet('key');
+ expect(result).toEqual(11);
+ expect(fetch.mock.lastCall).toMatchSnapshot();
+ });
+ it('handles failure', async () => {
+ const { cache, fetch } = buildCache();
+ fetch.mockResolvedValueOnce({ ok: false, text: async () => 'Not allowed', status: 403 });
+ await expect(cache.incrementAndGet('key')).rejects.toMatchError(new cache_1.ApiError(403, 'UNKNOWN_ERROR', 'Not allowed'));
+ expect(fetch.mock.lastCall).toMatchSnapshot();
+ });
+ });
+ describe('decrementAndGet', () => {
+ it('handles success', async () => {
+ const { cache, fetch } = buildCache();
+ fetch.mockResolvedValueOnce({ ok: true, text: async () => JSON.stringify({ response: 0 }), status: 200 });
+ const result = await cache.decrementAndGet('key');
+ expect(result).toEqual(0);
+ expect(fetch.mock.lastCall).toMatchSnapshot();
+ });
+ it('handles success with ttl', async () => {
+ const { cache, fetch } = buildCache();
+ fetch.mockResolvedValueOnce({ ok: true, text: async () => JSON.stringify({ response: 0 }), status: 200 });
+ const result = await cache.decrementAndGet('key', { ttlSeconds: 100 });
+ expect(result).toEqual(0);
+ expect(fetch.mock.lastCall).toMatchSnapshot();
+ });
+ it('handles success when key exists', async () => {
+ const { cache, fetch } = buildCache();
+ fetch.mockResolvedValueOnce({ ok: true, text: async () => JSON.stringify({ response: 11 }), status: 200 });
+ const result = await cache.decrementAndGet('key');
+ expect(result).toEqual(11);
+ expect(fetch.mock.lastCall).toMatchSnapshot();
+ });
+ it('handles failure', async () => {
+ const { cache, fetch } = buildCache();
+ fetch.mockResolvedValueOnce({ ok: false, text: async () => 'Not allowed', status: 403 });
+ await expect(cache.decrementAndGet('key')).rejects.toMatchError(new cache_1.ApiError(403, 'UNKNOWN_ERROR', 'Not allowed'));
+ expect(fetch.mock.lastCall).toMatchSnapshot();
+ });
+ });
+ describe('delete', () => {
+ it('handles success', async () => {
+ const { cache, fetch } = buildCache();
+ fetch.mockResolvedValueOnce({ ok: true, text: async () => JSON.stringify({ response: 1 }), status: 200 });
+ const result = await cache.delete('key');
+ expect(result).toEqual(1);
+ expect(fetch.mock.lastCall).toMatchSnapshot();
+ });
+ it('handles failure', async () => {
+ const { cache, fetch } = buildCache();
+ fetch.mockResolvedValueOnce({ ok: false, text: async () => 'Not allowed', status: 403 });
+ await expect(cache.delete('key')).rejects.toMatchError(new cache_1.ApiError(403, 'UNKNOWN_ERROR', 'Not allowed'));
+ expect(fetch.mock.lastCall).toMatchSnapshot();
+ });
+ });
+ describe('scan', () => {
+ it('handles success', async () => {
+ const { cache, fetch } = buildCache();
+ const expectedResponse = { cursor: '0', keys: ['key1', 'key2'] };
+ fetch.mockResolvedValueOnce({ ok: true, text: async () => JSON.stringify(expectedResponse), status: 200 });
+ const actualResponse = await cache.scan('key*');
+ expect(actualResponse).toEqual(expectedResponse);
+ expect(fetch.mock.lastCall).toMatchSnapshot();
+ });
+ it('handles success with cursor', async () => {
+ const { cache, fetch } = buildCache();
+ const expectedResponse = { cursor: '0', keys: ['key2'] };
+ fetch.mockResolvedValueOnce({ ok: true, text: async () => JSON.stringify(expectedResponse), status: 200 });
+ const actualResponse = await cache.scan('key*', { cursor: '1' });
+ expect(actualResponse).toEqual(expectedResponse);
+ expect(fetch.mock.lastCall).toMatchSnapshot();
+ });
+ it('handles success with count', async () => {
+ const { cache, fetch } = buildCache();
+ const expectedResponse = { cursor: '0', keys: ['key1'] };
+ fetch.mockResolvedValueOnce({ ok: true, text: async () => JSON.stringify(expectedResponse), status: 200 });
+ const actualResponse = await cache.scan('key*', { count: 1 });
+ expect(actualResponse).toEqual(expectedResponse);
+ expect(fetch.mock.lastCall).toMatchSnapshot();
+ });
+ it('handles failure', async () => {
+ const { cache, fetch } = buildCache();
+ fetch.mockResolvedValueOnce({ ok: false, text: async () => 'Not allowed', status: 403 });
+ await expect(cache.scan('key*', { cursor: '0', count: 2 })).rejects.toMatchError(new cache_1.ApiError(403, 'UNKNOWN_ERROR', 'Not allowed'));
+ expect(fetch.mock.lastCall).toMatchSnapshot();
+ });
+ });
+ describe('leftPush', () => {
+ it('handles success', async () => {
+ const { cache, fetch } = buildCache();
+ fetch.mockResolvedValueOnce({ ok: true, text: async () => JSON.stringify({ response: 1 }), status: 200 });
+ const result = await cache.leftPush('list', 'value');
+ expect(result).toEqual(1);
+ expect(fetch.mock.lastCall).toMatchSnapshot();
+ });
+ it('handles failure', async () => {
+ const { cache, fetch } = buildCache();
+ fetch.mockResolvedValueOnce({ ok: false, text: async () => 'Not allowed', status: 403 });
+ await expect(cache.leftPush('list', 'value')).rejects.toMatchError(new cache_1.ApiError(403, 'UNKNOWN_ERROR', 'Not allowed'));
+ expect(fetch.mock.lastCall).toMatchSnapshot();
+ });
+ });
+ describe('rightPop', () => {
+ it('handles success', async () => {
+ const { cache, fetch } = buildCache();
+ fetch.mockResolvedValueOnce({ ok: true, text: async () => JSON.stringify({ response: 'value' }), status: 200 });
+ const result = await cache.rightPop('list');
+ expect(result).toEqual('value');
+ expect(fetch.mock.lastCall).toMatchSnapshot();
+ });
+ it('handles success when key does not exist', async () => {
+ const { cache, fetch } = buildCache();
+ fetch.mockResolvedValueOnce({ ok: true, text: async () => JSON.stringify({ response: null }), status: 200 });
+ const result = await cache.rightPop('list');
+ expect(result).toBeNull();
+ expect(fetch.mock.lastCall).toMatchSnapshot();
+ });
+ it('handles failure', async () => {
+ const { cache, fetch } = buildCache();
+ fetch.mockResolvedValueOnce({ ok: false, text: async () => 'Not allowed', status: 403 });
+ await expect(cache.rightPop('list')).rejects.toMatchError(new cache_1.ApiError(403, 'UNKNOWN_ERROR', 'Not allowed'));
+ expect(fetch.mock.lastCall).toMatchSnapshot();
+ });
+ });
+ describe('listLength', () => {
+ it('handles success', async () => {
+ const { cache, fetch } = buildCache();
+ fetch.mockResolvedValueOnce({ ok: true, text: async () => JSON.stringify({ response: 2 }), status: 200 });
+ const result = await cache.listLength('list');
+ expect(result).toBe(2);
+ expect(fetch.mock.lastCall).toMatchSnapshot();
+ });
+ it('handles success when key does not exist', async () => {
+ const { cache, fetch } = buildCache();
+ fetch.mockResolvedValueOnce({ ok: true, text: async () => JSON.stringify({ response: 0 }), status: 200 });
+ const result = await cache.listLength('list');
+ expect(result).toBe(0);
+ expect(fetch.mock.lastCall).toMatchSnapshot();
+ });
+ it('handles failure', async () => {
+ const { cache, fetch } = buildCache();
+ fetch.mockResolvedValueOnce({ ok: false, text: async () => 'Not allowed', status: 403 });
+ await expect(cache.listLength('list')).rejects.toMatchError(new cache_1.ApiError(403, 'UNKNOWN_ERROR', 'Not allowed'));
+ expect(fetch.mock.lastCall).toMatchSnapshot();
+ });
+ });
+});