mirror of
https://github.com/HeyPuter/puter.git
synced 2026-05-04 00:20:45 +00:00
e75ccb0a41
* feat: root level kv accesses, and installed app listing * fix: revert server health check
286 lines
12 KiB
TypeScript
286 lines
12 KiB
TypeScript
// kv.test.ts - Tests for Puter KV module (set, get, del, incr, decr, list, flush)
|
|
import { describe, expect, it } from 'vitest';
|
|
import { puter } from './testUtils.js';
|
|
describe('Puter KV Module', () => {
|
|
|
|
const TEST_KEY = 'test-key';
|
|
it('should set a key success', async () => {
|
|
await expect(puter.kv.set(TEST_KEY, 0)).resolves.toBe(true);
|
|
});
|
|
|
|
it('should get a key success', async () => {
|
|
const getRes = await puter.kv.get(TEST_KEY);
|
|
expect(getRes).toBe(0);
|
|
});
|
|
it('should get empty key', async () => {
|
|
const emptyRes = await puter.kv.get(`fake${ TEST_KEY}`);
|
|
expect(emptyRes).toBeNull();
|
|
});
|
|
|
|
it('should increment a key success', async () => {
|
|
await puter.kv.set(TEST_KEY, 0);
|
|
const incrRes = await puter.kv.incr(TEST_KEY, { '': 5 });
|
|
expect(incrRes).toBe(5);
|
|
const finalGet = await puter.kv.get(TEST_KEY);
|
|
expect(finalGet).toBe(5);
|
|
});
|
|
|
|
it('should decrement a key success', async () => {
|
|
await puter.kv.set(TEST_KEY, 0);
|
|
const decrRes = await puter.kv.decr(TEST_KEY, { '': 3 });
|
|
expect(decrRes).toBe(-3);
|
|
const finalGet = await puter.kv.get(TEST_KEY);
|
|
expect(finalGet).toBe(-3);
|
|
});
|
|
|
|
it('should increment a key with second argument', async () => {
|
|
await puter.kv.set(TEST_KEY, 0);
|
|
const incrRes = await puter.kv.incr(TEST_KEY);
|
|
expect(incrRes).toBe(1);
|
|
const finalGet = await puter.kv.get(TEST_KEY);
|
|
expect(finalGet).toBe(1);
|
|
});
|
|
|
|
it('should decrement a key with second argument', async () => {
|
|
await puter.kv.set(TEST_KEY, 0);
|
|
const incrRes = await puter.kv.decr(TEST_KEY);
|
|
expect(incrRes).toBe(-1);
|
|
const finalGet = await puter.kv.get(TEST_KEY);
|
|
expect(finalGet).toBe(-1);
|
|
});
|
|
|
|
it('should increment a key with second argument', async () => {
|
|
await puter.kv.set(TEST_KEY, 0);
|
|
const incrRes = await puter.kv.incr(TEST_KEY, 2);
|
|
expect(incrRes).toBe(2);
|
|
const finalGet = await puter.kv.get(TEST_KEY);
|
|
expect(finalGet).toBe(2);
|
|
});
|
|
|
|
it('should decrement a key with second argument', async () => {
|
|
await puter.kv.set(TEST_KEY, 0);
|
|
const incrRes = await puter.kv.decr(TEST_KEY, 3);
|
|
expect(incrRes).toBe(-3);
|
|
const finalGet = await puter.kv.get(TEST_KEY);
|
|
expect(finalGet).toBe(-3);
|
|
});
|
|
|
|
it('should increment a key with nested path', async () => {
|
|
await puter.kv.set(TEST_KEY, { a: { b: 0 } });
|
|
const incrRes = await puter.kv.incr(TEST_KEY, { 'a.b': 1 });
|
|
expect(incrRes).toEqual({ a: { b: 1 } });
|
|
const finalGet = await puter.kv.get(TEST_KEY);
|
|
expect(finalGet).toEqual({ a: { b: 1 } });
|
|
});
|
|
|
|
it('should decrement a key with nested path', async () => {
|
|
await puter.kv.set(TEST_KEY, { a: { b: 0 } });
|
|
const incrRes = await puter.kv.decr(TEST_KEY, { 'a.b': 1 });
|
|
expect(incrRes).toEqual({ a: { b: -1 } });
|
|
const finalGet = await puter.kv.get(TEST_KEY);
|
|
expect(finalGet).toEqual({ a: { b: -1 } });
|
|
});
|
|
|
|
it('should increment a nonexistent key with nested path', async () => {
|
|
const incrRes = await puter.kv.incr(TEST_KEY + 1, { 'a.b': 1 });
|
|
expect(incrRes).toEqual({ a: { b: 1 } });
|
|
const finalGet = await puter.kv.get(TEST_KEY + 1);
|
|
expect(finalGet).toEqual({ a: { b: 1 } });
|
|
});
|
|
|
|
it('should decrement a nonexistent key with nested path', async () => {
|
|
const incrRes = await puter.kv.decr(TEST_KEY + 2, { 'a.b': 1 });
|
|
expect(incrRes).toEqual({ a: { b: -1 } });
|
|
const finalGet = await puter.kv.get(TEST_KEY + 2);
|
|
expect(finalGet).toEqual({ a: { b: -1 } });
|
|
});
|
|
|
|
it('should update a key with nested path', async () => {
|
|
const updateKey = `${TEST_KEY}-update`;
|
|
await puter.kv.set(updateKey, { profile: { name: 'old' } });
|
|
const updateRes = await puter.kv.update(updateKey, { 'profile.name': 'new' });
|
|
expect(updateRes).toEqual({ profile: { name: 'new' } });
|
|
const finalGet = await puter.kv.get(updateKey);
|
|
expect(finalGet).toEqual({ profile: { name: 'new' } });
|
|
});
|
|
|
|
it('should update a key with indexed path', async () => {
|
|
const updateKey = `${TEST_KEY}-update-index`;
|
|
await puter.kv.set(updateKey, { a: { b: [1, 2] } });
|
|
const updateRes = await puter.kv.update(updateKey, { 'a.b[1]': 5 });
|
|
expect(updateRes).toEqual({ a: { b: [1, 5] } });
|
|
const finalGet = await puter.kv.get(updateKey);
|
|
expect(finalGet).toEqual({ a: { b: [1, 5] } });
|
|
});
|
|
|
|
it('should add values into list paths', async () => {
|
|
const addKey = `${TEST_KEY}-add`;
|
|
const addRes = await puter.kv.add(addKey, { 'a.b': 1 });
|
|
expect(addRes).toEqual({ a: { b: [1] } });
|
|
const secondAdd = await puter.kv.add(addKey, { 'a.b': 2, 'a.c': ['x'] });
|
|
expect(secondAdd).toEqual({ a: { b: [1, 2], c: ['x'] } });
|
|
const finalGet = await puter.kv.get(addKey);
|
|
expect(finalGet).toEqual({ a: { b: [1, 2], c: ['x'] } });
|
|
});
|
|
|
|
it('should add values into indexed list paths', async () => {
|
|
const addKey = `${TEST_KEY}-add-index`;
|
|
await puter.kv.set(addKey, { a: { b: [[1], [2]] } });
|
|
const addRes = await puter.kv.add(addKey, { 'a.b[1]': 3 });
|
|
expect(addRes).toEqual({ a: { b: [[1], [2, 3]] } });
|
|
const finalGet = await puter.kv.get(addKey);
|
|
expect(finalGet).toEqual({ a: { b: [[1], [2, 3]] } });
|
|
});
|
|
|
|
it('should remove nested values including indexed list paths', async () => {
|
|
const removeKey = `${TEST_KEY}-remove`;
|
|
await puter.kv.set(removeKey, { a: { b: [1, 2, 3], c: { d: 4 }, e: 'keep' } });
|
|
const removeRes = await puter.kv.remove(removeKey, 'a.b[1]', 'a.c');
|
|
expect(removeRes).toEqual({ a: { b: [1, 3], e: 'keep' } });
|
|
const finalGet = await puter.kv.get(removeKey);
|
|
expect(finalGet).toEqual({ a: { b: [1, 3], e: 'keep' } });
|
|
});
|
|
|
|
it('should list keys', async () => {
|
|
const listRes = await puter.kv.list();
|
|
expect(Array.isArray(listRes)).toBe(true);
|
|
expect(listRes.length).toBeGreaterThan(0);
|
|
expect((listRes as string[]).includes(TEST_KEY)).toBe(true);
|
|
});
|
|
it('should support prefix pattern semantics', async () => {
|
|
const basePrefix = `${TEST_KEY}-pattern-abc`;
|
|
const abcKeys = [
|
|
`${basePrefix}`,
|
|
`${basePrefix}123`,
|
|
`${basePrefix}123xyz`,
|
|
];
|
|
await Promise.all(abcKeys.map((key) => puter.kv.set(key, key)));
|
|
await puter.kv.set(`${TEST_KEY}-pattern-ab`, 'nope');
|
|
|
|
const listNoWildcard = await puter.kv.list(basePrefix);
|
|
const listWildcard = await puter.kv.list(`${basePrefix}*`);
|
|
|
|
expect(Array.isArray(listNoWildcard)).toBe(true);
|
|
expect(Array.isArray(listWildcard)).toBe(true);
|
|
expect(listNoWildcard).toEqual(expect.arrayContaining(abcKeys));
|
|
expect(listWildcard).toEqual(expect.arrayContaining(abcKeys));
|
|
expect((listNoWildcard as string[]).every((key) => key.startsWith(basePrefix))).toBe(true);
|
|
expect((listWildcard as string[]).every((key) => key.startsWith(basePrefix))).toBe(true);
|
|
|
|
const literalStarPrefix = `${TEST_KEY}-pattern-key*`;
|
|
const literalStarKeys = [
|
|
`${literalStarPrefix}one`,
|
|
`${literalStarPrefix}two`,
|
|
];
|
|
await Promise.all(literalStarKeys.map((key) => puter.kv.set(key, key)));
|
|
|
|
const literalStarList = await puter.kv.list(`${literalStarPrefix}*`);
|
|
expect(Array.isArray(literalStarList)).toBe(true);
|
|
expect(literalStarList).toEqual(expect.arrayContaining(literalStarKeys));
|
|
expect((literalStarList as string[]).every((key) => key.startsWith(literalStarPrefix))).toBe(true);
|
|
|
|
const middleStarPrefix = `${TEST_KEY}-pattern-k*y`;
|
|
const middleStarKeys = [
|
|
`${middleStarPrefix}`,
|
|
`${middleStarPrefix}-more`,
|
|
];
|
|
await Promise.all(middleStarKeys.map((key) => puter.kv.set(key, key)));
|
|
|
|
const middleStarList = await puter.kv.list(`${middleStarPrefix}*`);
|
|
expect(Array.isArray(middleStarList)).toBe(true);
|
|
expect(middleStarList).toEqual(expect.arrayContaining(middleStarKeys));
|
|
expect((middleStarList as string[]).every((key) => key.startsWith(middleStarPrefix))).toBe(true);
|
|
|
|
const allList = await puter.kv.list('*');
|
|
expect(Array.isArray(allList)).toBe(true);
|
|
expect(allList).toEqual(expect.arrayContaining([...abcKeys, ...literalStarKeys, ...middleStarKeys]));
|
|
});
|
|
it('should list keys with pagination', async () => {
|
|
const pageKeys = [
|
|
`${TEST_KEY}-page-1`,
|
|
`${TEST_KEY}-page-2`,
|
|
`${TEST_KEY}-page-3`,
|
|
];
|
|
await puter.kv.set(pageKeys[0], 'one');
|
|
await puter.kv.set(pageKeys[1], 'two');
|
|
await puter.kv.set(pageKeys[2], 'three');
|
|
|
|
const firstPage = await puter.kv.list({ limit: 1 });
|
|
expect(Array.isArray(firstPage)).toBe(false);
|
|
|
|
const firstPageObj = firstPage as { items: string[]; cursor?: string };
|
|
expect(Array.isArray(firstPageObj.items)).toBe(true);
|
|
expect(firstPageObj.items.length).toBeLessThanOrEqual(1);
|
|
expect(firstPageObj.cursor).toBeTypeOf('string');
|
|
|
|
const secondPage = await puter.kv.list({ limit: 1, cursor: firstPageObj.cursor });
|
|
const secondPageObj = secondPage as { items: string[]; cursor?: string };
|
|
expect(Array.isArray(secondPageObj.items)).toBe(true);
|
|
expect(secondPageObj.items.length).toBeLessThanOrEqual(1);
|
|
});
|
|
it('should isolate namespaces when using optConfig.appUuid', async () => {
|
|
const suffix = Date.now().toString(36);
|
|
const key = `${TEST_KEY}-opt-override-${suffix}`;
|
|
const overrideA = { appUuid: `opt-a-${suffix}` };
|
|
const overrideB = { appUuid: `opt-b-${suffix}` };
|
|
|
|
await puter.kv.set(key, 'default-value');
|
|
await puter.kv.set(key, 'override-a-value', overrideA);
|
|
await puter.kv.set(key, 'override-b-value', overrideB);
|
|
|
|
expect(await puter.kv.get(key)).toBe('default-value');
|
|
expect(await puter.kv.get(key, overrideA)).toBe('override-a-value');
|
|
expect(await puter.kv.get(key, overrideB)).toBe('override-b-value');
|
|
|
|
const listA = await puter.kv.list(`${key}*`, overrideA);
|
|
expect(Array.isArray(listA)).toBe(true);
|
|
expect((listA as string[])).toContain(key);
|
|
|
|
await puter.kv.del(key, overrideA);
|
|
expect(await puter.kv.get(key, overrideA)).toBeNull();
|
|
expect(await puter.kv.get(key)).toBe('default-value');
|
|
});
|
|
it('should support optConfig shorthand and scoped flush', async () => {
|
|
const suffix = Date.now().toString(36);
|
|
const overrideA = { appUuid: `opt-shorthand-a-${suffix}` };
|
|
const overrideB = { appUuid: `opt-shorthand-b-${suffix}` };
|
|
const counterKey = `${TEST_KEY}-opt-counter-${suffix}`;
|
|
const updateKey = `${TEST_KEY}-opt-update-${suffix}`;
|
|
const flushKeyA = `${TEST_KEY}-opt-flush-a-${suffix}`;
|
|
const flushKeyB = `${TEST_KEY}-opt-flush-b-${suffix}`;
|
|
|
|
const incrRes = await puter.kv.incr(counterKey, overrideA);
|
|
expect(incrRes).toBe(1);
|
|
expect(await puter.kv.get(counterKey)).toBeNull();
|
|
expect(await puter.kv.get(counterKey, overrideA)).toBe(1);
|
|
|
|
const updateRes = await puter.kv.update(updateKey, { 'profile.name': 'Ada' }, overrideA);
|
|
expect(updateRes).toEqual({ profile: { name: 'Ada' } });
|
|
expect(await puter.kv.get(updateKey)).toBeNull();
|
|
expect(await puter.kv.get(updateKey, overrideA)).toEqual({ profile: { name: 'Ada' } });
|
|
|
|
await puter.kv.set(flushKeyA, 'A', overrideA);
|
|
await puter.kv.set(flushKeyB, 'B', overrideB);
|
|
await puter.kv.flush(overrideA);
|
|
|
|
expect(await puter.kv.get(flushKeyA, overrideA)).toBeNull();
|
|
expect(await puter.kv.get(flushKeyB, overrideB)).toBe('B');
|
|
});
|
|
// delete ops should go last
|
|
it('should flush all keys', async () => {
|
|
const flushRes = await puter.kv.flush();
|
|
expect(flushRes).toBe(true);
|
|
const postFlushList = await puter.kv.list();
|
|
expect(Array.isArray(postFlushList)).toBe(true);
|
|
expect(postFlushList.length).toBe(0);
|
|
});
|
|
it('should delete a key success', async () => {
|
|
const setRes = await puter.kv.set(TEST_KEY, 'to-be-deleted');
|
|
expect(setRes).toBe(true);
|
|
const delRes = await puter.kv.del(TEST_KEY);
|
|
expect(delRes).toBe(true);
|
|
const getRes = await puter.kv.get(TEST_KEY);
|
|
expect(getRes).toBeNull();
|
|
});
|
|
});
|