From 67c8c4d4dc226cef835b554fe2a8489e6ece8497 Mon Sep 17 00:00:00 2001 From: John Mosesman Date: Fri, 11 Feb 2022 12:49:16 -0600 Subject: [PATCH 1/3] Handle IPFS top-level with IPFS image URL --- src/constants/reasons.ts | 9 ++++++++- src/index.test.ts | 25 ++++++++++++++++++++----- src/index.ts | 14 +++++++++++++- tests/fixtures/sample_token_uris.ts | 10 +++++++++- 4 files changed, 50 insertions(+), 8 deletions(-) diff --git a/src/constants/reasons.ts b/src/constants/reasons.ts index fcfd1f1..771a27e 100644 --- a/src/constants/reasons.ts +++ b/src/constants/reasons.ts @@ -27,7 +27,13 @@ const tokenUriIsHttp: Reason = { const imageUriIsHttp: Reason = { id: "image-uri-is-http", severity: Severity.Critical, - message: "imageURI is hosted on a private server over HTTP", + message: "ImageURI is hosted on a private server over HTTP", +}; + +const imageUriIsIpfs: Reason = { + id: "image-uri-is-ipfs", + severity: Severity.Good, + message: "ImageURI is hosted on IPFS", }; export { @@ -36,4 +42,5 @@ export { tokenUriIsIpfs, tokenUriIsHttp, imageUriIsHttp, + imageUriIsIpfs, }; diff --git a/src/index.test.ts b/src/index.test.ts index a5bec3b..181a077 100644 --- a/src/index.test.ts +++ b/src/index.test.ts @@ -5,6 +5,7 @@ import { metadataOnChain, tokenUriIsHttp, tokenUriIsIpfs, + imageUriIsIpfs, } from "./constants/reasons"; import { analyzeTokenUri, isTokenUriBase64Json, isTokenUriHttp } from "./index"; @@ -12,6 +13,7 @@ import { analyzeTokenUri, isTokenUriBase64Json, isTokenUriHttp } from "./index"; import { ALL_ON_CHAIN, HTTP_WITH_HTTP_RESPONSE, + IMAGE_IS_IPFS_RESPONSE, } from "../tests/fixtures/sample_token_uris"; import axios from "axios"; @@ -62,17 +64,30 @@ describe("All on chain", () => { }); describe("IPFS tokenURI", () => { - test("IPFS url as the top level", async () => { - let result = await analyzeTokenUri("ipfs://bafybeic26wp7ck2/1234"); + // TODO: refactor/cleanup? + afterEach(() => { + jest.clearAllMocks(); + }); + + test("with IPFS URL for the image", async () => { + // @ts-ignore + axios.get.mockResolvedValueOnce(IMAGE_IS_IPFS_RESPONSE); + let result = await analyzeTokenUri("ipfs://blabhalsdkj/1234"); + + expect(axios.get).toHaveBeenCalledWith("ipfs://blabhalsdkj/1234"); expect(result.grade).toBe(GradeLetter.B); - expect(result.reasons.length).toEqual(1); + expect(result.reasons.length).toEqual(2); - const reason = result.reasons.find( + const reason1 = result.reasons.find( (reason) => reason.id === tokenUriIsIpfs.id ); + const reason2 = result.reasons.find( + (reason) => reason.id === imageUriIsIpfs.id + ); - expect(reason).toMatchObject(tokenUriIsIpfs); + expect(reason1).toMatchObject(tokenUriIsIpfs); + expect(reason2).toMatchObject(imageUriIsIpfs); }); }); diff --git a/src/index.ts b/src/index.ts index 0d407d3..a647851 100644 --- a/src/index.ts +++ b/src/index.ts @@ -32,6 +32,18 @@ export const handleBase64Json = (tokenUri: string): Reason[] => { return reasons; }; +export const handleIpfs = async (tokenUri: string): Promise => { + let reasons: Reason[] = [Reasons.tokenUriIsIpfs]; + + let res: Metadata = await axios.get(tokenUri); + + if (res.image && isTokenUriIpfs(res.image)) { + reasons = [...reasons, Reasons.imageUriIsIpfs]; + } + + return reasons; +}; + export const handleHttp = async (tokenUri: string): Promise => { let reasons: Reason[] = [Reasons.tokenUriIsHttp]; @@ -50,7 +62,7 @@ const analyzeTokenUri = async (tokenUri: string): Promise => { if (isTokenUriBase64Json(tokenUri)) { reasons = handleBase64Json(tokenUri); } else if (isTokenUriIpfs(tokenUri)) { - reasons = [Reasons.tokenUriIsIpfs]; + reasons = await handleIpfs(tokenUri); } else if (isTokenUriHttp(tokenUri)) { reasons = await handleHttp(tokenUri); } else { diff --git a/tests/fixtures/sample_token_uris.ts b/tests/fixtures/sample_token_uris.ts index 31e9c20..fb49292 100644 --- a/tests/fixtures/sample_token_uris.ts +++ b/tests/fixtures/sample_token_uris.ts @@ -9,4 +9,12 @@ const HTTP_WITH_HTTP_RESPONSE = { attributes: [{ trait_type: "Lazy", value: "true" }], }; -export { ALL_ON_CHAIN, HTTP_WITH_HTTP_RESPONSE }; +const IMAGE_IS_IPFS_RESPONSE = { + name: "#1234", + description: "Not Lazy Noodles", + external_url: "#", + image: "ipfs://bloopbllooopbloop/1234", + attributes: [{ trait_type: "Not Lazy", value: "true" }], +}; + +export { ALL_ON_CHAIN, HTTP_WITH_HTTP_RESPONSE, IMAGE_IS_IPFS_RESPONSE }; From 57fdf977807c92e1e3dc4f736b0578fe2850ddd3 Mon Sep 17 00:00:00 2001 From: John Mosesman Date: Fri, 11 Feb 2022 12:52:42 -0600 Subject: [PATCH 2/3] Cleanup mock afterEach --- src/index.test.ts | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/src/index.test.ts b/src/index.test.ts index 181a077..545d580 100644 --- a/src/index.test.ts +++ b/src/index.test.ts @@ -19,6 +19,10 @@ import { import axios from "axios"; jest.mock("axios"); +afterEach(() => { + jest.clearAllMocks(); +}); + describe("#isTokenUriBase64Json", () => { test("valid base64", async () => { expect( @@ -64,11 +68,6 @@ describe("All on chain", () => { }); describe("IPFS tokenURI", () => { - // TODO: refactor/cleanup? - afterEach(() => { - jest.clearAllMocks(); - }); - test("with IPFS URL for the image", async () => { // @ts-ignore axios.get.mockResolvedValueOnce(IMAGE_IS_IPFS_RESPONSE); @@ -92,9 +91,6 @@ describe("IPFS tokenURI", () => { }); describe("HTTP link for tokenURI", () => { - afterEach(() => { - jest.clearAllMocks(); - }); test("Random URL at top level results in poor grad", async () => { // @ts-ignore axios.get.mockResolvedValueOnce(HTTP_WITH_HTTP_RESPONSE); From 1b05d637fa23feabeaecc2869887fb23259fb2ef Mon Sep 17 00:00:00 2001 From: John Mosesman Date: Fri, 11 Feb 2022 12:53:31 -0600 Subject: [PATCH 3/3] Update readme --- readme.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/readme.md b/readme.md index 76cc6c3..c84b322 100644 --- a/readme.md +++ b/readme.md @@ -24,9 +24,9 @@ Within each top-level data structure, handle: - [ ] IPFS - - Image stored on that IPFS hash - - Image stored on anohter IPFS - - Image stored on rando server + - [ ] Image stored on that IPFS hash + - [x] Image stored on anohter IPFS + - [ ] Image stored on rando server - [ ] Http