@graffiti-garden/api 1.0.0 → 1.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +1 -1
- package/dist/index.cjs.map +4 -4
- package/dist/index.mjs +1 -1
- package/dist/index.mjs.map +4 -4
- package/dist/src/1-api.d.ts +66 -19
- package/dist/src/1-api.d.ts.map +1 -1
- package/dist/src/2-types.d.ts +3 -3
- package/dist/src/2-types.d.ts.map +1 -1
- package/dist/src/3-errors.d.ts +0 -9
- package/dist/src/3-errors.d.ts.map +1 -1
- package/dist/src/4-utilities.d.ts +8 -0
- package/dist/src/4-utilities.d.ts.map +1 -0
- package/dist/src/index.d.ts +1 -0
- package/dist/src/index.d.ts.map +1 -1
- package/dist/tests/crud.d.ts.map +1 -1
- package/dist/tests/index.d.ts +3 -0
- package/dist/tests/index.d.ts.map +1 -1
- package/dist/tests/media.d.ts +3 -0
- package/dist/tests/media.d.ts.map +1 -0
- package/dist/tests.mjs +683 -0
- package/dist/tests.mjs.map +4 -4
- package/package.json +3 -2
- package/src/1-api.ts +67 -17
- package/src/2-types.ts +4 -3
- package/src/3-errors.ts +0 -24
- package/src/4-utilities.ts +65 -0
- package/src/index.ts +1 -0
- package/tests/crud.ts +0 -15
- package/tests/index.ts +3 -2
- package/tests/media.ts +158 -0
package/dist/tests.mjs.map
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
|
-
"sources": ["../tests/utils.ts"],
|
|
4
|
-
"sourcesContent": ["import { assert } from \"vitest\";\nimport type {\n GraffitiPostObject,\n GraffitiObjectStream,\n JSONSchema,\n GraffitiObject,\n GraffitiObjectStreamReturn,\n GraffitiObjectStreamContinue,\n Graffiti,\n GraffitiSession,\n} from \"@graffiti-garden/api\";\n\nexport function randomString(): string {\n const array = new Uint8Array(16);\n crypto.getRandomValues(array);\n const str = Array.from(array)\n .map((b) => b.toString(16).padStart(2, \"0\"))\n .join(\"\");\n\n // check for unicode support\n return str + \"\uD83D\uDC69\uD83C\uDFFD\u200D\u2764\uFE0F\u200D\uD83D\uDC8B\u200D\uD83D\uDC69\uD83C\uDFFB\uD83E\uDEF1\uD83C\uDFFC\u200D\uD83E\uDEF2\uD83C\uDFFF\";\n}\n\nexport function randomValue() {\n return {\n [randomString()]: randomString(),\n };\n}\n\nexport function randomPostObject(): GraffitiPostObject<{}> {\n return {\n value: randomValue(),\n channels: [randomString(), randomString()],\n };\n}\n\nexport async function nextStreamValue<Schema extends JSONSchema>(\n iterator: GraffitiObjectStream<Schema>,\n): Promise<GraffitiObject<Schema>> {\n const result = await iterator.next();\n assert(!result.done && !result.value.error, \"result has no value\");\n assert(!result.value.tombstone, \"result has been deleted!\");\n return result.value.object;\n}\n\nexport function continueStream<Schema extends JSONSchema>(\n graffiti: Pick<Graffiti, \"continueDiscover\">,\n streamReturn: GraffitiObjectStreamReturn<Schema>,\n type: \"cursor\" | \"continue\",\n session?: GraffitiSession | null,\n): GraffitiObjectStreamContinue<Schema> {\n if (type === \"cursor\") {\n return graffiti.continueDiscover(\n streamReturn.cursor,\n session,\n ) as unknown as GraffitiObjectStreamContinue<Schema>;\n } else {\n return streamReturn.continue();\n }\n}\n"],
|
|
5
|
-
"mappings": ";AAAA,SAAS,cAAc;AAYhB,SAAS,eAAuB;AACrC,QAAM,QAAQ,IAAI,WAAW,EAAE;AAC/B,SAAO,gBAAgB,KAAK;AAC5B,QAAM,MAAM,MAAM,KAAK,KAAK,EACzB,IAAI,CAAC,MAAM,EAAE,SAAS,EAAE,EAAE,SAAS,GAAG,GAAG,CAAC,EAC1C,KAAK,EAAE;AAGV,SAAO,MAAM;AACf;AAEO,SAAS,cAAc;AAC5B,SAAO;AAAA,IACL,CAAC,aAAa,CAAC,GAAG,aAAa;AAAA,EACjC;AACF;AAEO,SAAS,mBAA2C;AACzD,SAAO;AAAA,IACL,OAAO,YAAY;AAAA,IACnB,UAAU,CAAC,aAAa,GAAG,aAAa,CAAC;AAAA,EAC3C;AACF;AAEA,eAAsB,gBACpB,UACiC;AACjC,QAAM,SAAS,MAAM,SAAS,KAAK;AACnC,SAAO,CAAC,OAAO,QAAQ,CAAC,OAAO,MAAM,OAAO,qBAAqB;AACjE,SAAO,CAAC,OAAO,MAAM,WAAW,0BAA0B;AAC1D,SAAO,OAAO,MAAM;AACtB;AAEO,SAAS,eACd,UACA,cACA,MACA,SACsC;AACtC,MAAI,SAAS,UAAU;AACrB,WAAO,SAAS;AAAA,MACd,aAAa;AAAA,MACb;AAAA,IACF;AAAA,EACF,OAAO;AACL,WAAO,aAAa,SAAS;AAAA,EAC/B;AACF;",
|
|
6
|
-
"names": []
|
|
3
|
+
"sources": ["../tests/crud.ts", "../tests/utils.ts", "../tests/discover.ts", "../tests/media.ts"],
|
|
4
|
+
"sourcesContent": ["import { it, expect, describe, beforeAll } from \"vitest\";\nimport type {\n Graffiti,\n GraffitiSession,\n JSONSchema,\n} from \"@graffiti-garden/api\";\nimport {\n GraffitiErrorNotFound,\n GraffitiErrorSchemaMismatch,\n GraffitiErrorInvalidSchema,\n GraffitiErrorForbidden,\n} from \"@graffiti-garden/api\";\nimport { randomPostObject, randomString } from \"./utils\";\n\nexport const graffitiCRUDTests = (\n useGraffiti: () => Pick<Graffiti, \"post\" | \"get\" | \"delete\">,\n useSession1: () => GraffitiSession | Promise<GraffitiSession>,\n useSession2: () => GraffitiSession | Promise<GraffitiSession>,\n) => {\n describe.concurrent(\n \"CRUD\",\n {\n timeout: 20000,\n },\n () => {\n let graffiti: ReturnType<typeof useGraffiti>;\n let session: GraffitiSession;\n let session1: GraffitiSession;\n let session2: GraffitiSession;\n beforeAll(async () => {\n graffiti = useGraffiti();\n session1 = await useSession1();\n session = session1;\n session2 = await useSession2();\n });\n\n it(\"post, get, delete\", async () => {\n const value = {\n something: \"hello, world~ c:\",\n };\n const channels = [randomString(), randomString()];\n\n // Post the object\n const previous = await graffiti.post<{}>({ value, channels }, session);\n expect(previous.value).toEqual(value);\n expect(previous.channels).toEqual(channels);\n expect(previous.allowed).toEqual(undefined);\n expect(previous.actor).toEqual(session.actor);\n\n // Get it back\n const gotten = await graffiti.get(previous, {});\n expect(gotten.value).toEqual(value);\n expect(gotten.channels).toEqual([]);\n expect(gotten.allowed).toBeUndefined();\n expect(gotten.url).toEqual(previous.url);\n expect(gotten.actor).toEqual(previous.actor);\n\n // Delete it\n await graffiti.delete(gotten, session);\n\n // Get is not found\n await expect(graffiti.get(gotten, {})).rejects.toBeInstanceOf(\n GraffitiErrorNotFound,\n );\n\n // Delete it again\n await expect(graffiti.delete(gotten, session)).rejects.toThrow(\n GraffitiErrorNotFound,\n );\n });\n\n it(\"post then delete with wrong actor\", async () => {\n const posted = await graffiti.post<{}>(\n { value: {}, channels: [] },\n session2,\n );\n\n await expect(graffiti.delete(posted, session1)).rejects.toThrow(\n GraffitiErrorForbidden,\n );\n });\n\n it(\"post and get with schema\", async () => {\n const schema = {\n properties: {\n value: {\n properties: {\n something: {\n type: \"string\",\n },\n another: {\n type: \"array\",\n items: {\n type: \"number\",\n },\n },\n deeper: {\n type: \"object\",\n properties: {\n deepProp: {\n type: \"string\",\n },\n },\n required: [\"deepProp\"],\n },\n },\n required: [\"another\", \"deeper\"],\n },\n },\n } as const satisfies JSONSchema;\n\n const goodValue = {\n something: \"hello\",\n another: [1, 2, 3],\n deeper: {\n deepProp: \"hello\",\n },\n };\n\n const posted = await graffiti.post<typeof schema>(\n {\n value: goodValue,\n channels: [],\n },\n session,\n );\n const gotten = await graffiti.get(posted, schema);\n\n expect(gotten.value.something).toEqual(goodValue.something);\n expect(gotten.value.another).toEqual(goodValue.another);\n expect(gotten.value.another[0]).toEqual(1);\n expect(gotten.value.deeper.deepProp).toEqual(goodValue.deeper.deepProp);\n });\n\n it(\"post and get with invalid schema\", async () => {\n const posted = await graffiti.post<{}>(\n { value: {}, channels: [] },\n session,\n );\n await expect(\n graffiti.get(posted, {\n properties: {\n value: {\n //@ts-ignore\n type: \"asdf\",\n },\n },\n }),\n ).rejects.toThrow(GraffitiErrorInvalidSchema);\n });\n\n it(\"post and get with wrong schema\", async () => {\n const posted = await graffiti.post<{}>(\n {\n value: {\n hello: \"world\",\n },\n channels: [],\n },\n session,\n );\n\n await expect(\n graffiti.get(posted, {\n properties: {\n value: {\n properties: {\n hello: {\n type: \"number\",\n },\n },\n },\n },\n }),\n ).rejects.toThrow(GraffitiErrorSchemaMismatch);\n });\n\n it(\"post and get with empty access control\", async () => {\n const value = {\n um: \"hi\",\n };\n const allowed = [randomString()];\n const channels = [randomString()];\n const posted = await graffiti.post<{}>(\n { value, allowed, channels },\n session1,\n );\n\n // Get it with authenticated session\n const gotten = await graffiti.get(posted, {}, session1);\n expect(gotten.url).toEqual(posted.url);\n expect(gotten.actor).toEqual(session1.actor);\n expect(gotten.value).toEqual(value);\n expect(gotten.allowed).toEqual(allowed);\n expect(gotten.channels).toEqual(channels);\n\n // But not without session\n await expect(graffiti.get(posted, {})).rejects.toBeInstanceOf(\n GraffitiErrorNotFound,\n );\n\n // Or the wrong session\n await expect(graffiti.get(posted, {}, session2)).rejects.toBeInstanceOf(\n GraffitiErrorNotFound,\n );\n });\n\n it(\"post and get with specific access control\", async () => {\n const value = {\n um: \"hi\",\n };\n const allowed = [randomString(), session2.actor, randomString()];\n const channels = [randomString()];\n const posted = await graffiti.post<{}>(\n {\n value,\n allowed,\n channels,\n },\n session1,\n );\n\n // Get it with authenticated session\n const gotten = await graffiti.get(posted, {}, session1);\n expect(gotten.url).toEqual(posted.url);\n expect(gotten.actor).toEqual(session1.actor);\n expect(gotten.value).toEqual(value);\n expect(gotten.allowed).toEqual(allowed);\n expect(gotten.channels).toEqual(channels);\n\n // But not without session\n await expect(graffiti.get(posted, {})).rejects.toBeInstanceOf(\n GraffitiErrorNotFound,\n );\n\n const gotten2 = await graffiti.get(posted, {}, session2);\n expect(gotten.url).toEqual(posted.url);\n expect(gotten.actor).toEqual(session1.actor);\n expect(gotten2.value).toEqual(value);\n // They should only see that is is private to them\n expect(gotten2.allowed).toEqual([session2.actor]);\n // And not see any channels\n expect(gotten2.channels).toEqual([]);\n });\n },\n );\n};\n", "import { assert } from \"vitest\";\nimport type {\n GraffitiPostObject,\n GraffitiObjectStream,\n JSONSchema,\n GraffitiObject,\n GraffitiObjectStreamReturn,\n GraffitiObjectStreamContinue,\n Graffiti,\n GraffitiSession,\n} from \"@graffiti-garden/api\";\n\nexport function randomString(): string {\n const array = new Uint8Array(16);\n crypto.getRandomValues(array);\n const str = Array.from(array)\n .map((b) => b.toString(16).padStart(2, \"0\"))\n .join(\"\");\n\n // check for unicode support\n return str + \"\uD83D\uDC69\uD83C\uDFFD\u200D\u2764\uFE0F\u200D\uD83D\uDC8B\u200D\uD83D\uDC69\uD83C\uDFFB\uD83E\uDEF1\uD83C\uDFFC\u200D\uD83E\uDEF2\uD83C\uDFFF\";\n}\n\nexport function randomValue() {\n return {\n [randomString()]: randomString(),\n };\n}\n\nexport function randomPostObject(): GraffitiPostObject<{}> {\n return {\n value: randomValue(),\n channels: [randomString(), randomString()],\n };\n}\n\nexport async function nextStreamValue<Schema extends JSONSchema>(\n iterator: GraffitiObjectStream<Schema>,\n): Promise<GraffitiObject<Schema>> {\n const result = await iterator.next();\n assert(!result.done && !result.value.error, \"result has no value\");\n assert(!result.value.tombstone, \"result has been deleted!\");\n return result.value.object;\n}\n\nexport function continueStream<Schema extends JSONSchema>(\n graffiti: Pick<Graffiti, \"continueDiscover\">,\n streamReturn: GraffitiObjectStreamReturn<Schema>,\n type: \"cursor\" | \"continue\",\n session?: GraffitiSession | null,\n): GraffitiObjectStreamContinue<Schema> {\n if (type === \"cursor\") {\n return graffiti.continueDiscover(\n streamReturn.cursor,\n session,\n ) as unknown as GraffitiObjectStreamContinue<Schema>;\n } else {\n return streamReturn.continue();\n }\n}\n", "import { it, expect, describe, assert, beforeAll } from \"vitest\";\nimport type {\n Graffiti,\n GraffitiObjectBase,\n GraffitiSession,\n JSONSchema,\n} from \"@graffiti-garden/api\";\nimport {\n randomString,\n nextStreamValue,\n randomPostObject,\n continueStream,\n} from \"./utils\";\n\nexport const graffitiDiscoverTests = (\n useGraffiti: () => Pick<\n Graffiti,\n \"discover\" | \"post\" | \"delete\" | \"continueDiscover\"\n >,\n useSession1: () => GraffitiSession | Promise<GraffitiSession>,\n useSession2: () => GraffitiSession | Promise<GraffitiSession>,\n) => {\n describe.concurrent(\"discover\", { timeout: 20000 }, () => {\n let graffiti: ReturnType<typeof useGraffiti>;\n let session: GraffitiSession;\n let session1: GraffitiSession;\n let session2: GraffitiSession;\n beforeAll(async () => {\n graffiti = useGraffiti();\n session1 = await useSession1();\n session = session1;\n session2 = await useSession2();\n });\n\n it(\"discover nothing\", async () => {\n const iterator = graffiti.discover([], {});\n expect(await iterator.next()).toHaveProperty(\"done\", true);\n });\n\n it(\"discover single\", async () => {\n const object = randomPostObject();\n\n const posted = await graffiti.post<{}>(object, session);\n\n const queryChannels = [randomString(), object.channels[0]];\n const iterator = graffiti.discover<{}>(queryChannels, {});\n const value = await nextStreamValue<{}>(iterator);\n expect(value.url).toEqual(posted.url);\n expect(value.value).toEqual(object.value);\n expect(value.channels).toEqual([object.channels[0]]);\n expect(value.allowed).toBeUndefined();\n expect(value.actor).toEqual(session.actor);\n const result2 = await iterator.next();\n expect(result2.done).toBe(true);\n });\n\n it(\"discover wrong channel\", async () => {\n const object = randomPostObject();\n await graffiti.post<{}>(object, session);\n const iterator = graffiti.discover([randomString()], {});\n await expect(iterator.next()).resolves.toHaveProperty(\"done\", true);\n });\n\n it(\"discover not allowed\", async () => {\n const object = randomPostObject();\n object.allowed = [randomString(), randomString()];\n const posted = await graffiti.post<{}>(object, session1);\n\n const iteratorSession1 = graffiti.discover<{}>(\n object.channels,\n {},\n session1,\n );\n const value = await nextStreamValue<{}>(iteratorSession1);\n expect(value.url).toEqual(posted.url);\n expect(value.value).toEqual(object.value);\n expect(value.channels).toEqual(object.channels);\n expect(value.allowed).toEqual(object.allowed);\n expect(value.actor).toEqual(session1.actor);\n\n const iteratorSession2 = graffiti.discover(object.channels, {}, session2);\n expect(await iteratorSession2.next()).toHaveProperty(\"done\", true);\n\n const iteratorNoSession = graffiti.discover(object.channels, {});\n expect(await iteratorNoSession.next()).toHaveProperty(\"done\", true);\n });\n\n it(\"discover allowed\", async () => {\n const object = randomPostObject();\n object.allowed = [randomString(), session2.actor, randomString()];\n const posted = await graffiti.post<{}>(object, session1);\n\n const iteratorSession2 = graffiti.discover<{}>(\n object.channels,\n {},\n session2,\n );\n const value = await nextStreamValue<{}>(iteratorSession2);\n expect(value.url).toEqual(posted.url);\n expect(value.value).toEqual(object.value);\n expect(value.allowed).toEqual([session2.actor]);\n expect(value.channels).toEqual(object.channels);\n expect(value.actor).toEqual(session1.actor);\n });\n\n it(\"discover for actor\", async () => {\n const object1 = randomPostObject();\n const posted1 = await graffiti.post<{}>(object1, session1);\n\n const object2 = randomPostObject();\n object2.channels = object1.channels;\n const posted2 = await graffiti.post<{}>(object2, session2);\n\n const iterator = graffiti.discover<{}>(object1.channels, {\n properties: {\n actor: { const: posted1.actor },\n },\n });\n\n const value = await nextStreamValue<{}>(iterator);\n expect(value.url).toEqual(posted1.url);\n expect(value.url).not.toEqual(posted2.url);\n expect(value.value).toEqual(object1.value);\n await expect(iterator.next()).resolves.toHaveProperty(\"done\", true);\n });\n\n it(\"discover schema allowed, as and not as owner\", async () => {\n const object = randomPostObject();\n object.allowed = [randomString(), session2.actor, randomString()];\n await graffiti.post<{}>(object, session1);\n\n const iteratorSession1 = graffiti.discover<{}>(\n object.channels,\n {\n properties: {\n allowed: {\n minItems: 3,\n // Make sure session2.actor is in the allow list\n not: {\n items: {\n not: { const: session2.actor },\n },\n },\n },\n },\n },\n session1,\n );\n const value = await nextStreamValue<{}>(iteratorSession1);\n expect(value.value).toEqual(object.value);\n await expect(iteratorSession1.next()).resolves.toHaveProperty(\n \"done\",\n true,\n );\n\n const iteratorSession2BigAllow = graffiti.discover(\n object.channels,\n {\n properties: {\n allowed: {\n minItems: 3,\n },\n },\n },\n session2,\n );\n await expect(iteratorSession2BigAllow.next()).resolves.toHaveProperty(\n \"done\",\n true,\n );\n const iteratorSession2PeekOther = graffiti.discover(\n object.channels,\n {\n properties: {\n allowed: {\n not: {\n items: {\n not: { const: object.allowed[0] },\n },\n },\n },\n },\n },\n session2,\n );\n await expect(iteratorSession2PeekOther.next()).resolves.toHaveProperty(\n \"done\",\n true,\n );\n const iteratorSession2SmallAllowPeekSelf = graffiti.discover<{}>(\n object.channels,\n {\n properties: {\n allowed: {\n maxItems: 1,\n not: {\n items: {\n not: { const: session2.actor },\n },\n },\n },\n },\n },\n session2,\n );\n const value2 = await nextStreamValue<{}>(\n iteratorSession2SmallAllowPeekSelf,\n );\n expect(value2.value).toEqual(object.value);\n await expect(\n iteratorSession2SmallAllowPeekSelf.next(),\n ).resolves.toHaveProperty(\"done\", true);\n });\n\n it(\"discover schema channels, as and not as owner\", async () => {\n const object = randomPostObject();\n object.channels = [randomString(), randomString(), randomString()];\n await graffiti.post<{}>(object, session1);\n\n const iteratorSession1 = graffiti.discover<{}>(\n [object.channels[0], object.channels[2]],\n {\n properties: {\n channels: {\n minItems: 3,\n // Make sure channel 1 is in the allow list\n not: {\n items: {\n not: { const: object.channels[1] },\n },\n },\n },\n },\n },\n session1,\n );\n const value = await nextStreamValue<{}>(iteratorSession1);\n expect(value.value).toEqual(object.value);\n await expect(iteratorSession1.next()).resolves.toHaveProperty(\n \"done\",\n true,\n );\n\n const iteratorSession2BigAllow = graffiti.discover(\n [object.channels[0], object.channels[2]],\n {\n properties: {\n channels: {\n minItems: 3,\n },\n },\n },\n session2,\n );\n await expect(iteratorSession2BigAllow.next()).resolves.toHaveProperty(\n \"done\",\n true,\n );\n const iteratorSession2PeekOther = graffiti.discover(\n [object.channels[0], object.channels[2]],\n {\n properties: {\n channels: {\n not: {\n items: {\n not: { const: object.channels[1] },\n },\n },\n },\n },\n },\n session2,\n );\n await expect(iteratorSession2PeekOther.next()).resolves.toHaveProperty(\n \"done\",\n true,\n );\n const iteratorSession2SmallAllowPeekSelf = graffiti.discover<{}>(\n [object.channels[0], object.channels[2]],\n {\n properties: {\n allowed: {\n maxItems: 2,\n not: {\n items: {\n not: { const: object.channels[2] },\n },\n },\n },\n },\n },\n session2,\n );\n const value2 = await nextStreamValue<{}>(\n iteratorSession2SmallAllowPeekSelf,\n );\n expect(value2.value).toEqual(object.value);\n await expect(\n iteratorSession2SmallAllowPeekSelf.next(),\n ).resolves.toHaveProperty(\"done\", true);\n });\n\n it(\"discover query for empty allowed\", async () => {\n const publicO = randomPostObject();\n\n const publicSchema = {\n not: {\n required: [\"allowed\"],\n },\n } satisfies JSONSchema;\n\n await graffiti.post<{}>(publicO, session1);\n const iterator = graffiti.discover<{}>(\n publicO.channels,\n publicSchema,\n session1,\n );\n const value = await nextStreamValue<{}>(iterator);\n expect(value.value).toEqual(publicO.value);\n expect(value.allowed).toBeUndefined();\n await expect(iterator.next()).resolves.toHaveProperty(\"done\", true);\n\n const restricted = randomPostObject();\n restricted.allowed = [];\n await graffiti.post<{}>(restricted, session1);\n const iterator2 = graffiti.discover(\n restricted.channels,\n publicSchema,\n session1,\n );\n await expect(iterator2.next()).resolves.toHaveProperty(\"done\", true);\n });\n\n it(\"discover query for values\", async () => {\n const object1 = randomPostObject();\n object1.value = { test: randomString() };\n await graffiti.post<{}>(object1, session);\n\n const object2 = randomPostObject();\n object2.channels = object1.channels;\n object2.value = { test: randomString(), something: randomString() };\n await graffiti.post<{}>(object2, session);\n\n const object3 = randomPostObject();\n object3.channels = object1.channels;\n object3.value = { other: randomString(), something: randomString() };\n await graffiti.post<{}>(object3, session);\n\n const counts = new Map<string, number>();\n for (const property of [\"test\", \"something\", \"other\"] as const) {\n let count = 0;\n for await (const result of graffiti.discover(object1.channels, {\n properties: {\n value: {\n required: [property],\n },\n },\n })) {\n assert(!result.error, \"result has error\");\n if (property in result.object.value) {\n count++;\n }\n }\n counts.set(property, count);\n }\n\n expect(counts.get(\"test\")).toBe(2);\n expect(counts.get(\"something\")).toBe(2);\n expect(counts.get(\"other\")).toBe(1);\n });\n\n for (const continueType of [\"cursor\", \"continue\"] as const) {\n describe(`continue discover with ${continueType}`, () => {\n it(\"discover for deleted content\", async () => {\n const object = randomPostObject();\n\n const posted = await graffiti.post<{}>(object, session);\n\n const iterator1 = graffiti.discover<{}>(object.channels, {});\n const value1 = await nextStreamValue<{}>(iterator1);\n expect(value1.value).toEqual(object.value);\n const returnValue = await iterator1.next();\n assert(returnValue.done, \"value2 is not done\");\n\n await graffiti.delete(posted, session);\n\n const iterator = graffiti.discover(object.channels, {});\n await expect(iterator.next()).resolves.toHaveProperty(\"done\", true);\n\n const tombIterator = continueStream<{}>(\n graffiti,\n returnValue.value,\n continueType,\n );\n const value = await tombIterator.next();\n assert(!value.done && !value.value.error, \"value is done\");\n assert(value.value.tombstone, \"value is not tombstone\");\n expect(value.value.object.url).toEqual(posted.url);\n await expect(tombIterator.next()).resolves.toHaveProperty(\n \"done\",\n true,\n );\n });\n });\n }\n });\n};\n", "import {\n GraffitiErrorNotAcceptable,\n GraffitiErrorNotFound,\n GraffitiErrorTooLarge,\n type Graffiti,\n type GraffitiSession,\n} from \"@graffiti-garden/api\";\nimport { it, expect, describe, beforeAll } from \"vitest\";\nimport { randomString } from \"./utils\";\n\nexport const graffitiMediaTests = (\n useGraffiti: () => Pick<Graffiti, \"postMedia\" | \"getMedia\" | \"deleteMedia\">,\n useSession1: () => GraffitiSession | Promise<GraffitiSession>,\n useSession2: () => GraffitiSession | Promise<GraffitiSession>,\n) => {\n describe.concurrent(\n \"media\",\n {\n timeout: 20000,\n },\n () => {\n let graffiti: ReturnType<typeof useGraffiti>;\n let session: GraffitiSession;\n let session1: GraffitiSession;\n let session2: GraffitiSession;\n beforeAll(async () => {\n graffiti = useGraffiti();\n session1 = await useSession1();\n session = session1;\n session2 = await useSession2();\n });\n\n it(\"post, get, delete media\", async () => {\n // Post media\n const text = randomString();\n const data = new Blob([text], { type: \"text/plain\" });\n const mediaUrl = await graffiti.postMedia({ data }, session);\n\n // Get the media back\n const media = await graffiti.getMedia(mediaUrl, {});\n expect(await media.data.text()).toEqual(text);\n expect(media.data.type).toEqual(\"text/plain\");\n expect(media.allowed).toBeUndefined();\n expect(media.actor).toBe(session.actor);\n\n // Delete the media\n await graffiti.deleteMedia(mediaUrl, session);\n\n // Try to get the media again\n await expect(graffiti.getMedia(mediaUrl, {})).rejects.toThrow(\n GraffitiErrorNotFound,\n );\n });\n\n it(\"acceptable type\", async () => {\n const text = randomString();\n const data = new Blob([text], { type: \"text/plain\" });\n const mediaUrl = await graffiti.postMedia({ data }, session);\n\n const media = await graffiti.getMedia(mediaUrl, {\n accept: \"text/*\",\n });\n expect(await media.data.text()).toEqual(text);\n expect(media.data.type).toEqual(\"text/plain\");\n expect(media.allowed).toBeUndefined();\n expect(media.actor).toBe(session.actor);\n });\n\n it(\"unacceptable type\", async () => {\n const text = randomString();\n const data = new Blob([text], { type: \"text/plain\" });\n const mediaUrl = await graffiti.postMedia({ data }, session);\n\n await expect(\n graffiti.getMedia(mediaUrl, {\n accept: \"image/*\",\n }),\n ).rejects.toThrow(GraffitiErrorNotAcceptable);\n });\n\n it(\"acceptable size\", async () => {\n const text = randomString();\n const data = new Blob([text], { type: \"text/plain\" });\n const mediaUrl = await graffiti.postMedia({ data }, session);\n\n const media = await graffiti.getMedia(mediaUrl, {\n maxBytes: data.size,\n });\n expect(await media.data.text()).toEqual(text);\n expect(media.data.type).toEqual(\"text/plain\");\n expect(media.allowed).toBeUndefined();\n expect(media.actor).toBe(session.actor);\n });\n\n it(\"unacceptable size\", async () => {\n const text = randomString();\n const data = new Blob([text], { type: \"text/plain\" });\n const mediaUrl = await graffiti.postMedia({ data }, session);\n\n await expect(\n graffiti.getMedia(mediaUrl, {\n maxBytes: data.size - 1,\n }),\n ).rejects.toThrow(GraffitiErrorTooLarge);\n });\n\n it(\"empty allowed\", async () => {\n const text = randomString();\n const data = new Blob([text], { type: \"text/plain\" });\n const allowed: string[] = [];\n const mediaUrl = await graffiti.postMedia({ data, allowed }, session1);\n\n // Get it with the authorized user\n const media = await graffiti.getMedia(mediaUrl, {}, session1);\n expect(await media.data.text()).toEqual(text);\n expect(media.data.type).toEqual(\"text/plain\");\n expect(media.allowed).toEqual([]);\n expect(media.actor).toBe(session1.actor);\n\n // Get it with the unauthorized user\n await expect(graffiti.getMedia(mediaUrl, {}, session2)).rejects.toThrow(\n GraffitiErrorNotFound,\n );\n\n // Get it without authorization\n await expect(graffiti.getMedia(mediaUrl, {})).rejects.toThrow(\n GraffitiErrorNotFound,\n );\n });\n\n it(\"allowed\", async () => {\n const text = randomString();\n const data = new Blob([text], { type: \"text/plain\" });\n const allowed = [randomString(), session2.actor, randomString()];\n const mediaUrl = await graffiti.postMedia({ data, allowed }, session1);\n\n // Get it with the authorized user\n const media = await graffiti.getMedia(mediaUrl, {}, session1);\n expect(await media.data.text()).toEqual(text);\n expect(media.data.type).toEqual(\"text/plain\");\n expect(media.allowed).toEqual(allowed);\n expect(media.actor).toBe(session1.actor);\n\n // Get it with the allowed user\n const media2 = await graffiti.getMedia(mediaUrl, {}, session2);\n expect(await media2.data.text()).toEqual(text);\n expect(media2.data.type).toEqual(\"text/plain\");\n expect(media2.allowed).toEqual([session2.actor]);\n expect(media2.actor).toBe(session1.actor);\n\n // Get it without authorization\n await expect(graffiti.getMedia(mediaUrl, {})).rejects.toThrow(\n GraffitiErrorNotFound,\n );\n });\n },\n );\n};\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": ["it", "expect", "describe", "assert", "beforeAll", "describe", "beforeAll", "it", "expect", "assert", "GraffitiErrorNotFound", "it", "expect", "describe", "beforeAll", "describe", "beforeAll", "it", "expect", "GraffitiErrorNotFound"]
|
|
7
7
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@graffiti-garden/api",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.2",
|
|
4
4
|
"description": "The heart of Graffiti",
|
|
5
5
|
"types": "./dist/src/index.d.ts",
|
|
6
6
|
"module": "./dist/index.mjs",
|
|
@@ -52,9 +52,10 @@
|
|
|
52
52
|
"tsx": "^4.21.0",
|
|
53
53
|
"typedoc": "^0.28.15",
|
|
54
54
|
"typescript": "^5.9.3",
|
|
55
|
-
"vitest": "^
|
|
55
|
+
"vitest": "^4.0.16"
|
|
56
56
|
},
|
|
57
57
|
"dependencies": {
|
|
58
|
+
"ajv": "^8.17.1",
|
|
58
59
|
"json-schema-to-ts": "^3.1.1"
|
|
59
60
|
}
|
|
60
61
|
}
|
package/src/1-api.ts
CHANGED
|
@@ -103,7 +103,7 @@ import type { JSONSchema } from "json-schema-to-ts";
|
|
|
103
103
|
* @groupDescription 3 - Media Methods
|
|
104
104
|
* Methods for {@link postMedia | creating}, {@link getMedia | reading},
|
|
105
105
|
* and {@link deleteMedia | deleting} media data.
|
|
106
|
-
* @groupDescription 4 -
|
|
106
|
+
* @groupDescription 4 - Identity Methods
|
|
107
107
|
* Methods and properties for logging in and out.
|
|
108
108
|
*/
|
|
109
109
|
export abstract class Graffiti {
|
|
@@ -118,12 +118,14 @@ export abstract class Graffiti {
|
|
|
118
118
|
*/
|
|
119
119
|
abstract post<Schema extends JSONSchema>(
|
|
120
120
|
/**
|
|
121
|
-
* An object to post
|
|
122
|
-
*
|
|
121
|
+
* An object to post, minus its {@link GraffitiObjectBase.url | `url`} and
|
|
122
|
+
* {@link GraffitiObjectBase.actor | `actor`}, which will be assigned once posted.
|
|
123
|
+
* This object is statically type-checked against the [JSON schema](https://json-schema.org/) that can be optionally provided
|
|
124
|
+
* as the generic type parameter. It is recommended to use a schema to
|
|
123
125
|
* ensure that the posted object matches subsequent {@link get} or {@link discover}
|
|
124
126
|
* methods.
|
|
125
127
|
*/
|
|
126
|
-
|
|
128
|
+
partialObject: GraffitiPostObject<Schema>,
|
|
127
129
|
/**
|
|
128
130
|
* An implementation-specific object with information to authenticate the
|
|
129
131
|
* {@link GraffitiObjectBase.actor | `actor`}.
|
|
@@ -284,12 +286,24 @@ export abstract class Graffiti {
|
|
|
284
286
|
* @group 3 - Media Methods
|
|
285
287
|
*/
|
|
286
288
|
abstract postMedia(
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
289
|
+
media: {
|
|
290
|
+
/**
|
|
291
|
+
* The binary data of the media to be uploaded,
|
|
292
|
+
* along with its [media type](https://www.iana.org/assignments/media-types/media-types.xhtml),
|
|
293
|
+
* formatted as a [Blob](https://developer.mozilla.org/en-US/docs/Web/API/Blob).
|
|
294
|
+
*/
|
|
295
|
+
data: Blob;
|
|
296
|
+
/**
|
|
297
|
+
* An optional list, identical in function to an object's
|
|
298
|
+
* {@link GraffitiObjectBase.allowed | `allowed`} property,
|
|
299
|
+
* that specifies the {@link GraffitiObjectBase.actor | `actor`}s
|
|
300
|
+
* who are allowed to access the media. If the list is `undefined`
|
|
301
|
+
* or `null`, anyone with the URL can access the media. If the list
|
|
302
|
+
* is empty, only the {@link GraffitiObjectBase.actor | `actor`}
|
|
303
|
+
* who {@link postMedia | `post`ed} the media can access it.
|
|
304
|
+
*/
|
|
305
|
+
allowed?: string[] | null;
|
|
306
|
+
},
|
|
293
307
|
/**
|
|
294
308
|
* An implementation-specific object with information to authenticate the
|
|
295
309
|
* {@link GraffitiObjectBase.actor | `actor`}.
|
|
@@ -341,22 +355,28 @@ export abstract class Graffiti {
|
|
|
341
355
|
*/
|
|
342
356
|
mediaUrl: string,
|
|
343
357
|
/**
|
|
344
|
-
*
|
|
358
|
+
* A set of requirements the retrieved media must meet.
|
|
345
359
|
*/
|
|
346
|
-
requirements
|
|
360
|
+
requirements: {
|
|
347
361
|
/**
|
|
348
362
|
* A list of acceptable media types for the retrieved media,
|
|
349
363
|
* formatted as like an [HTTP Accept header](https://httpwg.org/specs/rfc9110.html#field.accept)
|
|
350
364
|
*/
|
|
351
365
|
accept?: string;
|
|
352
366
|
/**
|
|
353
|
-
* The maximum size, in bytes, of the media.
|
|
367
|
+
* The maximum acceptable size, in bytes, of the media.
|
|
354
368
|
*/
|
|
355
369
|
maxBytes?: number;
|
|
356
370
|
},
|
|
371
|
+
/**
|
|
372
|
+
* An implementation-specific object with information to authenticate the
|
|
373
|
+
* {@link GraffitiObjectBase.actor | `actor`}.
|
|
374
|
+
*/
|
|
375
|
+
session?: GraffitiSession | null,
|
|
357
376
|
): Promise<{
|
|
358
|
-
|
|
377
|
+
data: Blob;
|
|
359
378
|
actor: string;
|
|
379
|
+
allowed?: string[] | null;
|
|
360
380
|
}>;
|
|
361
381
|
|
|
362
382
|
/**
|
|
@@ -369,7 +389,7 @@ export abstract class Graffiti {
|
|
|
369
389
|
* asynchronously via {@link Graffiti.sessionEvents | sessionEvents}
|
|
370
390
|
* as a {@link GraffitiLoginEvent} with event type `login`.
|
|
371
391
|
*
|
|
372
|
-
* @group 4 -
|
|
392
|
+
* @group 4 - Identity Methods
|
|
373
393
|
*/
|
|
374
394
|
abstract login(
|
|
375
395
|
/**
|
|
@@ -394,7 +414,7 @@ export abstract class Graffiti {
|
|
|
394
414
|
* {@link Graffiti.sessionEvents | sessionEvents}
|
|
395
415
|
* as a {@link GraffitiLogoutEvent} as event type `logout`.
|
|
396
416
|
*
|
|
397
|
-
* @group 4 -
|
|
417
|
+
* @group 4 - Identity Methods
|
|
398
418
|
*/
|
|
399
419
|
abstract logout(
|
|
400
420
|
/**
|
|
@@ -410,7 +430,37 @@ export abstract class Graffiti {
|
|
|
410
430
|
* - `logout` - {@link GraffitiLogoutEvent}
|
|
411
431
|
* - `initialized` - {@link GraffitiSessionInitializedEvent}
|
|
412
432
|
*
|
|
413
|
-
* @group 4 -
|
|
433
|
+
* @group 4 - Identity Methods
|
|
414
434
|
*/
|
|
415
435
|
abstract readonly sessionEvents: EventTarget;
|
|
436
|
+
|
|
437
|
+
/**
|
|
438
|
+
* Retrieves the human-readable handle associated
|
|
439
|
+
* with the given actor. The handle may change over time
|
|
440
|
+
* and so it should be used for display purposes only.
|
|
441
|
+
*
|
|
442
|
+
* The inverse of {@link handleToActor}.
|
|
443
|
+
*
|
|
444
|
+
* @throws {@link GraffitiErrorNotFound} if a handle cannot be
|
|
445
|
+
* found for the given actor.
|
|
446
|
+
*
|
|
447
|
+
* @returns A human-readable handle for the given actor.
|
|
448
|
+
*
|
|
449
|
+
* @group 4 - Identity Methods
|
|
450
|
+
*/
|
|
451
|
+
abstract actorToHandle(actor: string): Promise<string>;
|
|
452
|
+
|
|
453
|
+
/**
|
|
454
|
+
* Retrieves the actor ID associated with the given handle.
|
|
455
|
+
*
|
|
456
|
+
* The inverse of {@link actorToHandle}.
|
|
457
|
+
*
|
|
458
|
+
* @throws {@link GraffitiErrorNotFound} if there is no actor
|
|
459
|
+
* with the given handle.
|
|
460
|
+
*
|
|
461
|
+
* @returns The actor ID for the given handle.
|
|
462
|
+
*
|
|
463
|
+
* @group 4 - Identity Methods
|
|
464
|
+
*/
|
|
465
|
+
abstract handleToActor(handle: string): Promise<string>;
|
|
416
466
|
}
|
package/src/2-types.ts
CHANGED
|
@@ -102,7 +102,7 @@ export interface GraffitiObjectBase {
|
|
|
102
102
|
* Schema-aware objects are returned by {@link Graffiti.get} and {@link Graffiti.discover}.
|
|
103
103
|
*/
|
|
104
104
|
export type GraffitiObject<Schema extends JSONSchema> = GraffitiObjectBase &
|
|
105
|
-
FromSchema<Schema & typeof
|
|
105
|
+
FromSchema<Schema & typeof GraffitiPostObjectJSONSchema>;
|
|
106
106
|
|
|
107
107
|
/**
|
|
108
108
|
* A JSON Schema equivalent to the {@link GraffitiObjectBase} type.
|
|
@@ -145,7 +145,6 @@ export type GraffitiPostObject<Schema extends JSONSchema> = Pick<
|
|
|
145
145
|
GraffitiObjectBase,
|
|
146
146
|
"value" | "channels" | "allowed"
|
|
147
147
|
> &
|
|
148
|
-
Partial<GraffitiObjectBase> &
|
|
149
148
|
FromSchema<Schema & typeof GraffitiPostObjectJSONSchema>;
|
|
150
149
|
|
|
151
150
|
/**
|
|
@@ -315,7 +314,9 @@ export interface GraffitiObjectStreamReturn<Schema extends JSONSchema> {
|
|
|
315
314
|
* @returns A function that creates new stream that continues from where the original stream left off.
|
|
316
315
|
* It preserves the typing of the original stream.
|
|
317
316
|
*/
|
|
318
|
-
continue: (
|
|
317
|
+
continue: (
|
|
318
|
+
session?: GraffitiSession | null,
|
|
319
|
+
) => GraffitiObjectStreamContinue<Schema>;
|
|
319
320
|
/**
|
|
320
321
|
* A string that can be serialized and stored to resume the stream later.
|
|
321
322
|
* It must be passed to the {@link Graffiti.continueDiscover} method
|
package/src/3-errors.ts
CHANGED
|
@@ -1,11 +1,3 @@
|
|
|
1
|
-
export class GraffitiErrorUnauthorized extends Error {
|
|
2
|
-
constructor(message?: string) {
|
|
3
|
-
super(message);
|
|
4
|
-
this.name = "GraffitiErrorUnauthorized";
|
|
5
|
-
Object.setPrototypeOf(this, GraffitiErrorUnauthorized.prototype);
|
|
6
|
-
}
|
|
7
|
-
}
|
|
8
|
-
|
|
9
1
|
export class GraffitiErrorForbidden extends Error {
|
|
10
2
|
constructor(message?: string) {
|
|
11
3
|
super(message);
|
|
@@ -53,19 +45,3 @@ export class GraffitiErrorNotAcceptable extends Error {
|
|
|
53
45
|
Object.setPrototypeOf(this, GraffitiErrorNotAcceptable.prototype);
|
|
54
46
|
}
|
|
55
47
|
}
|
|
56
|
-
|
|
57
|
-
export class GraffitiErrorInvalidUrl extends Error {
|
|
58
|
-
constructor(message?: string) {
|
|
59
|
-
super(message);
|
|
60
|
-
this.name = "GraffitiErrorInvalidUrl";
|
|
61
|
-
Object.setPrototypeOf(this, GraffitiErrorInvalidUrl.prototype);
|
|
62
|
-
}
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
export class GraffitiErrorUnrecognizedUrlScheme extends Error {
|
|
66
|
-
constructor(message?: string) {
|
|
67
|
-
super(message);
|
|
68
|
-
this.name = "GraffitiErrorUnrecognizedUriScheme";
|
|
69
|
-
Object.setPrototypeOf(this, GraffitiErrorUnrecognizedUrlScheme.prototype);
|
|
70
|
-
}
|
|
71
|
-
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import type { JSONSchema } from "json-schema-to-ts";
|
|
2
|
+
import type { Ajv } from "ajv";
|
|
3
|
+
import { GraffitiErrorInvalidSchema } from "./3-errors";
|
|
4
|
+
import type {
|
|
5
|
+
GraffitiObjectBase,
|
|
6
|
+
GraffitiObject,
|
|
7
|
+
GraffitiObjectUrl,
|
|
8
|
+
GraffitiSession,
|
|
9
|
+
} from "./2-types";
|
|
10
|
+
|
|
11
|
+
export function unpackObjectUrl(url: string | GraffitiObjectUrl) {
|
|
12
|
+
return typeof url === "string" ? url : url.url;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function compileGraffitiObjectSchema<Schema extends JSONSchema>(
|
|
16
|
+
ajv: Ajv,
|
|
17
|
+
schema: Schema,
|
|
18
|
+
) {
|
|
19
|
+
try {
|
|
20
|
+
// Force the validation guard because
|
|
21
|
+
// it is too big for the type checker.
|
|
22
|
+
// Fortunately json-schema-to-ts is
|
|
23
|
+
// well tested against ajv.
|
|
24
|
+
return ajv.compile(schema) as (
|
|
25
|
+
data: GraffitiObjectBase,
|
|
26
|
+
) => data is GraffitiObject<Schema>;
|
|
27
|
+
} catch (error) {
|
|
28
|
+
throw new GraffitiErrorInvalidSchema(
|
|
29
|
+
error instanceof Error ? error.message : undefined,
|
|
30
|
+
);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export function isActorAllowedGraffitiObject(
|
|
35
|
+
object: GraffitiObjectBase,
|
|
36
|
+
session?: GraffitiSession | null,
|
|
37
|
+
) {
|
|
38
|
+
return (
|
|
39
|
+
// If there is no allowed list, the actor is allowed.
|
|
40
|
+
!Array.isArray(object.allowed) ||
|
|
41
|
+
// Otherwise...
|
|
42
|
+
(typeof session?.actor === "string" &&
|
|
43
|
+
// The actor must be the creator of the object
|
|
44
|
+
(object.actor === session.actor ||
|
|
45
|
+
// Or be on the allowed list
|
|
46
|
+
object.allowed.includes(session.actor)))
|
|
47
|
+
);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export function maskGraffitiObject(
|
|
51
|
+
object: GraffitiObjectBase,
|
|
52
|
+
channels: string[],
|
|
53
|
+
session?: GraffitiSession | null,
|
|
54
|
+
): void {
|
|
55
|
+
// If the actor is not the creator, mask the object.
|
|
56
|
+
if (object.actor !== session?.actor) {
|
|
57
|
+
// If there is an allowed list, mask it to only include the actor
|
|
58
|
+
// (This assumes the actor is already allowed to access the object)
|
|
59
|
+
object.allowed = object.allowed && session ? [session.actor] : undefined;
|
|
60
|
+
// Mask the channels to only include the channels that are being queried
|
|
61
|
+
object.channels = object.channels.filter((channel) =>
|
|
62
|
+
channels.includes(channel),
|
|
63
|
+
);
|
|
64
|
+
}
|
|
65
|
+
}
|
package/src/index.ts
CHANGED
package/tests/crud.ts
CHANGED
|
@@ -80,21 +80,6 @@ export const graffitiCRUDTests = (
|
|
|
80
80
|
);
|
|
81
81
|
});
|
|
82
82
|
|
|
83
|
-
it("post then delete object that is not allowed", async () => {
|
|
84
|
-
const posted = await graffiti.post<{}>(
|
|
85
|
-
{
|
|
86
|
-
value: {},
|
|
87
|
-
channels: [],
|
|
88
|
-
allowed: [],
|
|
89
|
-
},
|
|
90
|
-
session1,
|
|
91
|
-
);
|
|
92
|
-
|
|
93
|
-
await expect(graffiti.delete(posted, session2)).rejects.toThrow(
|
|
94
|
-
GraffitiErrorNotFound,
|
|
95
|
-
);
|
|
96
|
-
});
|
|
97
|
-
|
|
98
83
|
it("post and get with schema", async () => {
|
|
99
84
|
const schema = {
|
|
100
85
|
properties: {
|
package/tests/index.ts
CHANGED
package/tests/media.ts
ADDED
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
import {
|
|
2
|
+
GraffitiErrorNotAcceptable,
|
|
3
|
+
GraffitiErrorNotFound,
|
|
4
|
+
GraffitiErrorTooLarge,
|
|
5
|
+
type Graffiti,
|
|
6
|
+
type GraffitiSession,
|
|
7
|
+
} from "@graffiti-garden/api";
|
|
8
|
+
import { it, expect, describe, beforeAll } from "vitest";
|
|
9
|
+
import { randomString } from "./utils";
|
|
10
|
+
|
|
11
|
+
export const graffitiMediaTests = (
|
|
12
|
+
useGraffiti: () => Pick<Graffiti, "postMedia" | "getMedia" | "deleteMedia">,
|
|
13
|
+
useSession1: () => GraffitiSession | Promise<GraffitiSession>,
|
|
14
|
+
useSession2: () => GraffitiSession | Promise<GraffitiSession>,
|
|
15
|
+
) => {
|
|
16
|
+
describe.concurrent(
|
|
17
|
+
"media",
|
|
18
|
+
{
|
|
19
|
+
timeout: 20000,
|
|
20
|
+
},
|
|
21
|
+
() => {
|
|
22
|
+
let graffiti: ReturnType<typeof useGraffiti>;
|
|
23
|
+
let session: GraffitiSession;
|
|
24
|
+
let session1: GraffitiSession;
|
|
25
|
+
let session2: GraffitiSession;
|
|
26
|
+
beforeAll(async () => {
|
|
27
|
+
graffiti = useGraffiti();
|
|
28
|
+
session1 = await useSession1();
|
|
29
|
+
session = session1;
|
|
30
|
+
session2 = await useSession2();
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
it("post, get, delete media", async () => {
|
|
34
|
+
// Post media
|
|
35
|
+
const text = randomString();
|
|
36
|
+
const data = new Blob([text], { type: "text/plain" });
|
|
37
|
+
const mediaUrl = await graffiti.postMedia({ data }, session);
|
|
38
|
+
|
|
39
|
+
// Get the media back
|
|
40
|
+
const media = await graffiti.getMedia(mediaUrl, {});
|
|
41
|
+
expect(await media.data.text()).toEqual(text);
|
|
42
|
+
expect(media.data.type).toEqual("text/plain");
|
|
43
|
+
expect(media.allowed).toBeUndefined();
|
|
44
|
+
expect(media.actor).toBe(session.actor);
|
|
45
|
+
|
|
46
|
+
// Delete the media
|
|
47
|
+
await graffiti.deleteMedia(mediaUrl, session);
|
|
48
|
+
|
|
49
|
+
// Try to get the media again
|
|
50
|
+
await expect(graffiti.getMedia(mediaUrl, {})).rejects.toThrow(
|
|
51
|
+
GraffitiErrorNotFound,
|
|
52
|
+
);
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
it("acceptable type", async () => {
|
|
56
|
+
const text = randomString();
|
|
57
|
+
const data = new Blob([text], { type: "text/plain" });
|
|
58
|
+
const mediaUrl = await graffiti.postMedia({ data }, session);
|
|
59
|
+
|
|
60
|
+
const media = await graffiti.getMedia(mediaUrl, {
|
|
61
|
+
accept: "text/*",
|
|
62
|
+
});
|
|
63
|
+
expect(await media.data.text()).toEqual(text);
|
|
64
|
+
expect(media.data.type).toEqual("text/plain");
|
|
65
|
+
expect(media.allowed).toBeUndefined();
|
|
66
|
+
expect(media.actor).toBe(session.actor);
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
it("unacceptable type", async () => {
|
|
70
|
+
const text = randomString();
|
|
71
|
+
const data = new Blob([text], { type: "text/plain" });
|
|
72
|
+
const mediaUrl = await graffiti.postMedia({ data }, session);
|
|
73
|
+
|
|
74
|
+
await expect(
|
|
75
|
+
graffiti.getMedia(mediaUrl, {
|
|
76
|
+
accept: "image/*",
|
|
77
|
+
}),
|
|
78
|
+
).rejects.toThrow(GraffitiErrorNotAcceptable);
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
it("acceptable size", async () => {
|
|
82
|
+
const text = randomString();
|
|
83
|
+
const data = new Blob([text], { type: "text/plain" });
|
|
84
|
+
const mediaUrl = await graffiti.postMedia({ data }, session);
|
|
85
|
+
|
|
86
|
+
const media = await graffiti.getMedia(mediaUrl, {
|
|
87
|
+
maxBytes: data.size,
|
|
88
|
+
});
|
|
89
|
+
expect(await media.data.text()).toEqual(text);
|
|
90
|
+
expect(media.data.type).toEqual("text/plain");
|
|
91
|
+
expect(media.allowed).toBeUndefined();
|
|
92
|
+
expect(media.actor).toBe(session.actor);
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
it("unacceptable size", async () => {
|
|
96
|
+
const text = randomString();
|
|
97
|
+
const data = new Blob([text], { type: "text/plain" });
|
|
98
|
+
const mediaUrl = await graffiti.postMedia({ data }, session);
|
|
99
|
+
|
|
100
|
+
await expect(
|
|
101
|
+
graffiti.getMedia(mediaUrl, {
|
|
102
|
+
maxBytes: data.size - 1,
|
|
103
|
+
}),
|
|
104
|
+
).rejects.toThrow(GraffitiErrorTooLarge);
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
it("empty allowed", async () => {
|
|
108
|
+
const text = randomString();
|
|
109
|
+
const data = new Blob([text], { type: "text/plain" });
|
|
110
|
+
const allowed: string[] = [];
|
|
111
|
+
const mediaUrl = await graffiti.postMedia({ data, allowed }, session1);
|
|
112
|
+
|
|
113
|
+
// Get it with the authorized user
|
|
114
|
+
const media = await graffiti.getMedia(mediaUrl, {}, session1);
|
|
115
|
+
expect(await media.data.text()).toEqual(text);
|
|
116
|
+
expect(media.data.type).toEqual("text/plain");
|
|
117
|
+
expect(media.allowed).toEqual([]);
|
|
118
|
+
expect(media.actor).toBe(session1.actor);
|
|
119
|
+
|
|
120
|
+
// Get it with the unauthorized user
|
|
121
|
+
await expect(graffiti.getMedia(mediaUrl, {}, session2)).rejects.toThrow(
|
|
122
|
+
GraffitiErrorNotFound,
|
|
123
|
+
);
|
|
124
|
+
|
|
125
|
+
// Get it without authorization
|
|
126
|
+
await expect(graffiti.getMedia(mediaUrl, {})).rejects.toThrow(
|
|
127
|
+
GraffitiErrorNotFound,
|
|
128
|
+
);
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
it("allowed", async () => {
|
|
132
|
+
const text = randomString();
|
|
133
|
+
const data = new Blob([text], { type: "text/plain" });
|
|
134
|
+
const allowed = [randomString(), session2.actor, randomString()];
|
|
135
|
+
const mediaUrl = await graffiti.postMedia({ data, allowed }, session1);
|
|
136
|
+
|
|
137
|
+
// Get it with the authorized user
|
|
138
|
+
const media = await graffiti.getMedia(mediaUrl, {}, session1);
|
|
139
|
+
expect(await media.data.text()).toEqual(text);
|
|
140
|
+
expect(media.data.type).toEqual("text/plain");
|
|
141
|
+
expect(media.allowed).toEqual(allowed);
|
|
142
|
+
expect(media.actor).toBe(session1.actor);
|
|
143
|
+
|
|
144
|
+
// Get it with the allowed user
|
|
145
|
+
const media2 = await graffiti.getMedia(mediaUrl, {}, session2);
|
|
146
|
+
expect(await media2.data.text()).toEqual(text);
|
|
147
|
+
expect(media2.data.type).toEqual("text/plain");
|
|
148
|
+
expect(media2.allowed).toEqual([session2.actor]);
|
|
149
|
+
expect(media2.actor).toBe(session1.actor);
|
|
150
|
+
|
|
151
|
+
// Get it without authorization
|
|
152
|
+
await expect(graffiti.getMedia(mediaUrl, {})).rejects.toThrow(
|
|
153
|
+
GraffitiErrorNotFound,
|
|
154
|
+
);
|
|
155
|
+
});
|
|
156
|
+
},
|
|
157
|
+
);
|
|
158
|
+
};
|