Skip to content

Commit

Permalink
chore(sdk): Add unit tests for the subgraph calls on the SDK (#749)
Browse files Browse the repository at this point in the history
Co-authored-by: Alain Nicolas <[email protected]>
  • Loading branch information
satyajeetkolhapure and alainncls authored Oct 21, 2024
1 parent 54b83b0 commit de126c8
Show file tree
Hide file tree
Showing 14 changed files with 1,142 additions and 8 deletions.
71 changes: 66 additions & 5 deletions .github/workflows/sdk.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ on:
- release/*

jobs:
test-sdk:
unit-test-sdk:
runs-on: ubuntu-latest

defaults:
Expand Down Expand Up @@ -53,12 +53,73 @@ jobs:
run: pnpm install --frozen-lockfile

- name: Run the unit tests
run: pnpm run test:unit:ci
run: pnpm run test:unit:coverage

- name: Move the report at the root of the 'sdk' folder
run: mv coverage/lcov.info .

- name: Upload coverage report to Codecov
uses: codecov/codecov-action@v3
with:
files: ./sdk/lcov.info
token: ${{ secrets.CODECOV_TOKEN }}
fail_ci_if_error: true
verbose: true

- name: Check test coverage
uses: terencetcf/github-actions-lcov-minimum-coverage-checker@v1
with:
coverage-file: sdk/lcov.info
minimum-coverage: 50

- name: Add coverage summary
run: |
echo "## Coverage result" >> $GITHUB_STEP_SUMMARY
echo "✅ Uploaded to Codecov" >> $GITHUB_STEP_SUMMARY
integration-test-sdk:
runs-on: ubuntu-latest

defaults:
run:
working-directory: sdk

steps:
- name: Check out the repo
uses: actions/checkout@v3

- name: Install Pnpm
uses: pnpm/action-setup@v2
with:
version: 9
run_install: false

- name: Install Node.js
uses: actions/setup-node@v3
with:
node-version: 18
cache: pnpm

- name: Get pnpm store directory
shell: bash
run: |
echo "STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_ENV
- uses: actions/cache@v3
name: Setup pnpm cache
with:
path: ${{ env.STORE_PATH }}
key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
restore-keys: |
${{ runner.os }}-pnpm-store-
- name: Install dependencies
run: pnpm install --frozen-lockfile

- name: Run the integration tests
run: pnpm run test:integration:ci

- name: Add test summary
- name: Integration tests summary
run: |
echo "## Tests result" >> $GITHUB_STEP_SUMMARY
echo "✅ Passed" >> $GITHUB_STEP_SUMMARY
echo "## Integration tests result" >> $GITHUB_STEP_SUMMARY
echo "✅ All passed" >> $GITHUB_STEP_SUMMARY
1 change: 1 addition & 0 deletions sdk/.npmignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ node_modules
src
!dist/src
test
*.test.*
.env
.env.example
.graphclientrc.yml
Expand Down
2 changes: 2 additions & 0 deletions sdk/jest.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ const config = {
transform: {
".graphclient/index.ts": ["babel-jest", { configFile: "./babel-jest.config.cjs" }],
},
coverageDirectory: "coverage",
coverageReporters: ["lcov", "text"],
};

module.exports = config;
4 changes: 2 additions & 2 deletions sdk/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,8 @@
"test:ci": "cp .env.example .env && pnpm run test",
"test:integration": "jest integration",
"test:integration:ci": "cp .env.example .env && pnpm run test:integration",
"test:unit": "echo \"TODO\"",
"test:unit:ci": "cp .env.example .env && pnpm run test:unit"
"test:unit": "jest --testPathIgnorePatterns='integration'",
"test:unit:coverage": "pnpm run test:unit --coverage"
},
"dependencies": {
"@graphql-mesh/cache-localforage": "^0.95.8",
Expand Down
158 changes: 158 additions & 0 deletions sdk/src/dataMapper/AttestationDataMapper.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
import AttestationDataMapper from "./AttestationDataMapper";
import { Constants, SDKMode } from "../utils/constants";
import { decodeWithRetry } from "../utils/abiCoder";
import { getIPFSContent } from "../utils/ipfsClient";
import { Attestation, Conf } from "../types";
import BaseDataMapper from "./BaseDataMapper";
import { lineaSepolia } from "viem/chains";
import { PublicClient, WalletClient } from "viem";
import { VeraxSdk } from "../VeraxSdk";
import { off } from "process";

jest.mock("./BaseDataMapper");
jest.mock("../utils/abiCoder");
jest.mock("../utils/ipfsClient");

describe("AttestationDataMapper", () => {
let attestationDataMapper: AttestationDataMapper;
const mockConf: Conf = {
subgraphUrl: "http://mock-subgraph.com",
chain: lineaSepolia,
mode: SDKMode.BACKEND,
portalRegistryAddress: "0x1",
moduleRegistryAddress: "0x2",
schemaRegistryAddress: "0x3",
attestationRegistryAddress: "0x4",
};

const mockAttestation: Attestation = {
id: "1",
attestationId: "1",
replacedBy: null,
attester: "0xAttester",
attestedDate: 1634515200,
expirationDate: 1734515200,
revocationDate: 1634615200,
version: 1,
revoked: false,
subject: "0xSubject",
encodedSubject: "0xEncodedSubject",
attestationData: "0xAttestationData",
decodedData: [],
decodedPayload: {},
schema: {
id: Constants.OFFCHAIN_DATA_SCHEMA_ID,
name: "Test Schema",
description: "A test schema",
context: "http://schema.org",
schema: "schema",
attestationCounter: 1,
},
portal: {
id: "0xPortal",
ownerAddress: "0xOwner",
modules: ["0xModule"],
isRevocable: false,
name: "Test Portal",
description: "A test portal",
ownerName: "Owner",
attestationCounter: 1,
},
offchainData: undefined,
};

const mockWeb3Client = {} as PublicClient;
const mockWalletClient = {} as WalletClient;
const mockVeraxSdk = {} as VeraxSdk;

beforeEach(() => {
attestationDataMapper = new AttestationDataMapper(mockConf, mockWeb3Client, mockVeraxSdk, mockWalletClient);
jest.clearAllMocks();
});

describe("findOneById", () => {
it("should find attestation by id and enrich it", async () => {
(BaseDataMapper.prototype.findOneById as jest.Mock).mockResolvedValue(mockAttestation);
(decodeWithRetry as jest.Mock).mockReturnValue([
{ schemaId: Constants.OFFCHAIN_DATA_SCHEMA_ID, uri: "ipfs://QmHash" },
]);

const result = await attestationDataMapper.findOneById("1");

expect(BaseDataMapper.prototype.findOneById).toHaveBeenCalledWith("1");
expect(decodeWithRetry).toHaveBeenCalledWith(mockAttestation.schema.schema, mockAttestation.attestationData);
expect(result).toEqual(mockAttestation);
});

it("should return undefined if no attestation is found", async () => {
(BaseDataMapper.prototype.findOneById as jest.Mock).mockResolvedValue(undefined);

const result = await attestationDataMapper.findOneById("1");

expect(BaseDataMapper.prototype.findOneById).toHaveBeenCalledWith("1");
expect(result).toBeUndefined();
});
});

describe("findBy", () => {
it("should find attestations and enrich each of them", async () => {
const attestations = [mockAttestation];
(BaseDataMapper.prototype.findBy as jest.Mock).mockResolvedValue(attestations);
(decodeWithRetry as jest.Mock).mockReturnValue([
{ schemaId: Constants.OFFCHAIN_DATA_SCHEMA_ID, uri: "ipfs://QmHash" },
]);

const result = await attestationDataMapper.findBy(10, 0, {}, "attestedDate", "desc");

expect(BaseDataMapper.prototype.findBy).toHaveBeenCalledWith(10, 0, {}, "attestedDate", "desc");
expect(decodeWithRetry).toHaveBeenCalledTimes(1);
expect(result).toEqual(attestations);
});
});

describe("enrichAttestation", () => {
it("should enrich attestation with decoded payload for off-chain data schema", async () => {
(BaseDataMapper.prototype.findOneById as jest.Mock).mockResolvedValue(mockAttestation);
(decodeWithRetry as jest.Mock).mockReturnValue([
{ schemaId: Constants.OFFCHAIN_DATA_SCHEMA_ID, uri: "ipfs://QmHash" },
]);
(getIPFSContent as jest.Mock).mockResolvedValue("data");

const result = await attestationDataMapper.findOneById("1");

expect(result?.decodedPayload).toEqual("data");
expect(result?.offchainData).toEqual({
schemaId: Constants.OFFCHAIN_DATA_SCHEMA_ID,
uri: "ipfs://QmHash",
});
});

it("should set offchainData.error if IPFS request fails", async () => {
(decodeWithRetry as jest.Mock).mockReturnValue([
{ schemaId: Constants.OFFCHAIN_DATA_SCHEMA_ID, uri: "ipfs://QmHash" },
]);
(getIPFSContent as jest.Mock).mockRejectedValue(new Error("IPFS Error"));

// Call findOneById, which will trigger enrichAttestation
const result = await attestationDataMapper.findOneById("1");

expect(result?.offchainData?.error).toBe("IPFS Error");
});

it("should not enrich if schema ID is not offchain data", async () => {
const nonOffchainAttestation = {
...mockAttestation,
schema: { ...mockAttestation.schema, id: "0x123" },
offchainData: undefined,
};
(decodeWithRetry as jest.Mock).mockReturnValue([{}]);
(BaseDataMapper.prototype.findOneById as jest.Mock).mockResolvedValue(nonOffchainAttestation);

// Call findOneById, which will trigger enrichAttestation
const result = await attestationDataMapper.findOneById("1");

expect(result?.decodedPayload).toStrictEqual([{}]);
expect(result?.offchainData).toBeUndefined();
});
});
});
Loading

0 comments on commit de126c8

Please sign in to comment.