parse-rest-client.test.ts 3.1 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677
  1. import assert from 'node:assert/strict';
  2. import test from 'node:test';
  3. import { ParseRestClient, ParseRestError } from '../src/db/parse-rest.client.js';
  4. test('Parse REST client keeps the master key server-side and encodes structured queries', async () => {
  5. const calls: Array<{ url: string; init: RequestInit }> = [];
  6. const fetchMock: typeof fetch = async (input, init = {}) => {
  7. calls.push({ url: String(input), init });
  8. return new Response(JSON.stringify({ results: [] }), {
  9. status: 200,
  10. headers: { 'Content-Type': 'application/json' },
  11. });
  12. };
  13. const client = new ParseRestClient({
  14. serverUrl: 'https://parse.example.test/parse/',
  15. appId: 'app-id',
  16. masterKey: 'master-secret',
  17. }, fetchMock);
  18. await client.find('VocProduct', {
  19. where: { workspaceId: 'demashi', platform: 'jd' },
  20. order: 'productKey',
  21. limit: 25,
  22. });
  23. await client.request('/health', { master: false });
  24. const queryUrl = new URL(calls[0]!.url);
  25. assert.equal(queryUrl.pathname, '/parse/classes/VocProduct');
  26. assert.deepEqual(JSON.parse(queryUrl.searchParams.get('where')!), { workspaceId: 'demashi', platform: 'jd' });
  27. assert.equal(queryUrl.searchParams.get('order'), 'productKey');
  28. assert.equal(queryUrl.searchParams.get('limit'), '25');
  29. assert.equal((calls[0]!.init.headers as Record<string, string>)['X-Parse-Master-Key'], 'master-secret');
  30. assert.equal('X-Parse-Master-Key' in (calls[1]!.init.headers as Record<string, string>), false);
  31. });
  32. test('Parse REST batch requests stay under the upstream limit and preserve the mount path', async () => {
  33. let requestBody: { requests: Array<{ path: string }> } | null = null;
  34. const fetchMock: typeof fetch = async (_input, init = {}) => {
  35. requestBody = JSON.parse(String(init.body)) as { requests: Array<{ path: string }> };
  36. return new Response(JSON.stringify([{ success: { objectId: 'one' } }]), {
  37. status: 200,
  38. headers: { 'Content-Type': 'application/json' },
  39. });
  40. };
  41. const client = new ParseRestClient({
  42. serverUrl: 'https://parse.example.test/custom/parse',
  43. appId: 'app-id',
  44. masterKey: 'master-secret',
  45. }, fetchMock);
  46. await client.batch([{ method: 'POST', path: '/classes/VocWorkspace', body: { publicId: 'demashi' } }]);
  47. assert.equal(requestBody!.requests[0]!.path, '/custom/parse/classes/VocWorkspace');
  48. await assert.rejects(
  49. () => client.batch(Array.from({ length: 51 }, () => ({ method: 'DELETE' as const, path: '/classes/VocProduct/id' }))),
  50. /limited to 50/,
  51. );
  52. });
  53. test('Parse REST errors expose status and Parse code without returning raw response text', async () => {
  54. const client = new ParseRestClient({
  55. serverUrl: 'https://parse.example.test/parse',
  56. appId: 'app-id',
  57. masterKey: 'master-secret',
  58. }, async () => new Response(JSON.stringify({ code: 119, error: 'Permission denied' }), {
  59. status: 400,
  60. headers: { 'Content-Type': 'application/json' },
  61. }));
  62. await assert.rejects(
  63. () => client.find('VocWorkspace'),
  64. (error: unknown) => error instanceof ParseRestError
  65. && error.status === 400
  66. && error.code === 119
  67. && error.message === 'Permission denied',
  68. );
  69. });