From 8d14a1cfa3f0eefbbbfc59cb55835fa66370f5cd Mon Sep 17 00:00:00 2001 From: Hw <56923450+waddaboo@users.noreply.github.com> Date: Tue, 2 Jul 2024 15:59:34 +0800 Subject: [PATCH 1/6] test(credentials): add more tests to improve test coverage --- .../credentials/credentials.service.test.ts | 199 +++++++++++++++++- 1 file changed, 198 insertions(+), 1 deletion(-) diff --git a/apps/api/src/app/credentials/credentials.service.test.ts b/apps/api/src/app/credentials/credentials.service.test.ts index 46776498..ae8d7775 100644 --- a/apps/api/src/app/credentials/credentials.service.test.ts +++ b/apps/api/src/app/credentials/credentials.service.test.ts @@ -1,4 +1,8 @@ -import { getProvider, validateCredentials } from "@bandada/credentials" +import { + getProvider, + validateCredentials, + validateManyCredentials +} from "@bandada/credentials" import { ScheduleModule } from "@nestjs/schedule" import { Test } from "@nestjs/testing" import { TypeOrmModule } from "@nestjs/typeorm" @@ -177,6 +181,37 @@ describe("CredentialsService", () => { await expect(fun).rejects.toThrow(`OAuth state does not exist`) }) + it("Should throw an error if the credential group blockchain network is not supported", async () => { + const { id: _groupId } = await groupsService.createGroup( + { + name: "Group2", + description: "This is a description", + treeDepth: 16, + fingerprintDuration: 3600, + credentials: JSON.stringify({ + id: "BLOCKCHAIN_TRANSACTIONS", + criteria: { + minTransactions: 12, + network: "test_network" + } + }) + }, + "admin" + ) + + const _stateId = await credentialsService.setOAuthState({ + groupId: _groupId, + memberId: "123", + providerName: "blockchain" + }) + + const fun = credentialsService.addMember([_stateId], undefined, [ + "0x" + ]) + + await expect(fun).rejects.toThrow(`The network is not supported`) + }) + it("Should add a member to a credential group", async () => { const _stateId = await credentialsService.setOAuthState({ groupId, @@ -398,5 +433,167 @@ describe("CredentialsService", () => { expect(clientRedirectUri).toBeUndefined() }) + + it("Should not add a member to a group with many credentials and return undefined if the OAuth state does not match the credential provider", async () => { + const { id: _groupId } = await groupsService.createGroup( + { + name: "Group4", + description: "This is a description", + treeDepth: 16, + fingerprintDuration: 3600, + credentials: JSON.stringify({ + credentials: [ + { + id: "BLOCKCHAIN_TRANSACTIONS", + criteria: { + minTransactions: 12, + network: "sepolia" + } + }, + { + id: "GITHUB_FOLLOWERS", + criteria: { + minFollowers: 5 + } + } + ], + expression: ["", "and", ""] + }) + }, + "admin" + ) + + const _stateId1 = await credentialsService.setOAuthState({ + groupId: _groupId, + memberId: "1", + providerName: "blockchain" + }) + + const _stateId2 = await credentialsService.setOAuthState({ + groupId: _groupId, + memberId: "1", + providerName: "twitter" + }) + + const clientRedirectUri = await credentialsService.addMember( + [_stateId1, _stateId2], + ["code"], + ["0x"] + ) + + const group = await groupsService.getGroup(_groupId) + + expect(clientRedirectUri).toBeUndefined() + expect(group.members).toHaveLength(0) + }) + + it("Should throw an error if the group with many credentials contains unsupported network", async () => { + const { id: _groupId } = await groupsService.createGroup( + { + name: "Group5", + description: "This is a description", + treeDepth: 16, + fingerprintDuration: 3600, + credentials: JSON.stringify({ + credentials: [ + { + id: "BLOCKCHAIN_TRANSACTIONS", + criteria: { + minTransactions: 12, + network: "test_network" + } + }, + { + id: "GITHUB_FOLLOWERS", + criteria: { + minFollowers: 5 + } + } + ], + expression: ["", "and", ""] + }) + }, + "admin" + ) + + const _stateId1 = await credentialsService.setOAuthState({ + groupId: _groupId, + memberId: "1", + providerName: "blockchain" + }) + + const _stateId2 = await credentialsService.setOAuthState({ + groupId: _groupId, + memberId: "1", + providerName: "github" + }) + + const fun = credentialsService.addMember( + [_stateId1, _stateId2], + ["code"], + ["0x"] + ) + + await expect(fun).rejects.toThrow(`The network is not supported`) + }) + + it("Should throw an error if the OAuth account has already joined the group with many credentials", async () => { + const _stateId1 = await credentialsService.setOAuthState({ + groupId, + memberId: "123", + providerName: "blockchain" + }) + + const _stateId2 = await credentialsService.setOAuthState({ + groupId, + memberId: "123", + providerName: "github" + }) + + const fun = credentialsService.addMember( + [_stateId1, _stateId2], + ["code"] + ) + + await expect(fun).rejects.toThrow( + `OAuth account has already joined the group` + ) + }) + + it("Should throw an error if the OAuth account does not match the criteria of the group with many credentials", async () => { + ;(getProvider as any).mockImplementation( + () => + ({ + getAccessToken: jest.fn(() => "2"), + getProfile: jest.fn(() => ({ + id: "id2" + })) + } as any) + ) + ;(validateManyCredentials as any).mockImplementationOnce( + async () => false + ) + + const _stateId1 = await credentialsService.setOAuthState({ + groupId, + memberId: "124", + providerName: "blockchain" + }) + + const _stateId2 = await credentialsService.setOAuthState({ + groupId, + memberId: "124", + providerName: "github" + }) + + const fun = credentialsService.addMember( + [_stateId1, _stateId2], + ["code"] + ) + + await expect(fun).rejects.toThrow( + `OAuth account does not match criteria` + ) + }) }) }) From 8e1b8377190161d29a2d9025c7ce26ecc03dd256 Mon Sep 17 00:00:00 2001 From: Hw <56923450+waddaboo@users.noreply.github.com> Date: Tue, 2 Jul 2024 18:01:09 +0800 Subject: [PATCH 2/6] test(groups): add more tests to improve test coverage --- .../api/src/app/groups/groups.service.test.ts | 119 +++++++++++++++++- 1 file changed, 118 insertions(+), 1 deletion(-) diff --git a/apps/api/src/app/groups/groups.service.test.ts b/apps/api/src/app/groups/groups.service.test.ts index 5623ae29..4d353337 100644 --- a/apps/api/src/app/groups/groups.service.test.ts +++ b/apps/api/src/app/groups/groups.service.test.ts @@ -143,6 +143,7 @@ describe("GroupsService", () => { id, { description: "This is a new description", + treeDepth: 16, fingerprintDuration: 1000, credentials: { id: "TWITTER_FOLLOWERS", @@ -266,7 +267,7 @@ describe("GroupsService", () => { }) }) - describe("# addMember", () => { + describe("# joinGroup", () => { let invite: Invite beforeAll(async () => { @@ -309,6 +310,40 @@ describe("GroupsService", () => { }) }) + describe("# addMember", () => { + let _group: Group + const memberId = "123123" + + beforeAll(async () => { + _group = await groupsService.createGroup( + { + name: "MemberGroup", + description: "This is a description", + treeDepth: 16, + fingerprintDuration: 3600 + }, + "admin" + ) + }) + + it("Should add member to the group", async () => { + const { members } = await groupsService.addMember( + _group.id, + memberId + ) + + expect(members).toHaveLength(1) + }) + + it("Should not add member to a group if the member is already in the group", async () => { + const fun = groupsService.addMember(_group.id, memberId) + + await expect(fun).rejects.toThrow( + `Member '${memberId}' is already in the group '${_group.name}'` + ) + }) + }) + describe("# isGroupMember", () => { it("Should return false if a member does not exist", () => { const result = groupsService.isGroupMember(groupId, "123122") @@ -1162,6 +1197,34 @@ describe("GroupsService", () => { expect(members).toHaveLength(3) }) + it("Should not add a member to the group via API if the group is a credential group", async () => { + const _group = await groupsService.createGroup( + { + name: "Credential Group", + description: "This is a new group", + treeDepth: 16, + fingerprintDuration: 3600, + credentials: { + id: "GITHUB_FOLLOWERS", + criteria: { + minFollowers: 5 + } + } + }, + admin.id + ) + + const fun = groupsService.addMembersWithAPIKey( + _group.id, + ["123123", "456456", "789789"], + apiKey + ) + + await expect(fun).rejects.toThrow( + `The group '${_group.name}' is a credential group. You cannot add members to a credential group using an API Key.` + ) + }) + it("Should not add a member if they already exist", async () => { const fun = groupsService.addMembersWithAPIKey( group.id, @@ -1357,6 +1420,34 @@ describe("GroupsService", () => { ]) }) + it("Should not add a member to the group if the group is a credential group", async () => { + const _group = await groupsService.createGroup( + { + name: "Credential Group", + description: "This is a new group", + treeDepth: 16, + fingerprintDuration: 3600, + credentials: { + id: "GITHUB_FOLLOWERS", + criteria: { + minFollowers: 5 + } + } + }, + "admin" + ) + + const fun = groupsService.addMembersManually( + _group.id, + ["789789", "122121", "456456"], + "admin" + ) + + await expect(fun).rejects.toThrow( + `The group '${_group.name}' is a credential group. You cannot manually add members to a credential group.` + ) + }) + it("Should not add members if they already exists", async () => { const fun = groupsService.addMembersManually( group.id, @@ -1776,6 +1867,32 @@ describe("GroupsService", () => { }) }) + describe("# fingerprint", () => { + it("Should get fingerprint", async () => { + const fingerprint = await groupsService.getFingerprint(groupId) + + expect(fingerprint).toBeDefined() + }) + + it("Should get fingerprints", async () => { + const group = await groupsService.createGroup( + { + name: "Fingerprint Group", + description: "This is a description", + treeDepth: 16, + fingerprintDuration: 3600 + }, + "admin" + ) + const fingerprints = await groupsService.getFingerprints([ + groupId, + group.id + ]) + + expect(fingerprints).toHaveLength(2) + }) + }) + describe("# initialize", () => { it("Should initialize the cached groups", async () => { const currentCachedGroups = await groupsService.getGroups() From c0282c8ea5c958576e44653fe2471e989d696c3f Mon Sep 17 00:00:00 2001 From: Hw <56923450+waddaboo@users.noreply.github.com> Date: Thu, 4 Jul 2024 16:01:57 +0800 Subject: [PATCH 3/6] test(libs-credentials): add more tests to improve test coverage --- libs/credentials/src/index.test.ts | 122 ++++++++++++++++++++++++++++- 1 file changed, 121 insertions(+), 1 deletion(-) diff --git a/libs/credentials/src/index.test.ts b/libs/credentials/src/index.test.ts index eef550f8..fecee620 100644 --- a/libs/credentials/src/index.test.ts +++ b/libs/credentials/src/index.test.ts @@ -14,8 +14,11 @@ import { validateCredentials, validateManyCredentials } from "./validateCredentials" -import { testUtils } from "." +import { githubFollowers, testUtils } from "." import { evaluate, tokenize } from "./evaluateExpression" +import checkCriteria from "./checkCriteria" +import getJsonRpcProvider from "./getJsonRpcProvider" +import queryGraph from "./queryGraph" describe("Credentials library", () => { describe("# addProvider", () => { @@ -78,11 +81,115 @@ describe("Credentials library", () => { }) }) + describe("# getJsonRpcProvider", () => { + it("Should return a JSON-RPC Provider", () => { + const jsonRpcProvider = getJsonRpcProvider( + "https://rpc.sepolia.org" + ) + + expect(jsonRpcProvider).toBeDefined() + }) + }) + + describe("# checkCriteria", () => { + it("Should return undefined for a valid criteria", () => { + const criteria = { + minFollowers: 12 + } + const check = checkCriteria(criteria, githubFollowers.criteriaABI) + + expect(check).toBeUndefined() + }) + + it("Should return undefined for a valid criteria with multiple parameters", () => { + const criteria = { + minTransactions: 12, + network: "sepolia" + } + const check = checkCriteria( + criteria, + blockchainTransactions.criteriaABI + ) + + expect(check).toBeUndefined() + }) + + it("Should throw an error if the criteria parameter is undefined", () => { + const criteria = {} + const fun = () => + checkCriteria(criteria, githubFollowers.criteriaABI) + + expect(fun).toThrow(`Parameter 'minFollowers' has not been defined`) + }) + + it("Should throw an error if the criteria parameter is not a part of the criteria", () => { + const criteria = { + minFollowers: 12, + minBalance: "10" + } + const fun = () => + checkCriteria(criteria, githubFollowers.criteriaABI) + + expect(fun).toThrow( + `Parameter 'minBalance' should not be part of the criteria` + ) + }) + + it("Should throw an error if the criteria parameter has the wrong data type", () => { + const criteria = { + minFollowers: "12" + } + const fun = () => + checkCriteria(criteria, githubFollowers.criteriaABI) + + expect(fun).toThrow(`Parameter 'minFollowers' is not a number`) + }) + }) + + describe("# queryGraph", () => { + it("Should return a function that can be used to query graphs data using GraphQL", () => { + const query = queryGraph( + "https://easscan.org/graphql", + ` + query { + attestations { + recipient + attester + revocable + revoked + schemaId + isOffchain + } + } + ` + ) + + expect(query).toBeUndefined() + }) + }) + describe("# validateCredentials", () => { const jsonRpcProviderMocked = { getBalance: jest.fn(), getTransactionCount: jest.fn() } + it("Should throw an error if the credentials cannot be validated", async () => { + const fun = validateCredentials( + { + id: githubPersonalStars.id, + criteria: { + minStars: 100 + } + }, + { + address: "0x", + jsonRpcProvider: null + } + ) + + await expect(fun).rejects.toThrow("Credentials cannot be validated") + }) + it("Should return true if an account has a balance greater than or equal to 10", async () => { jsonRpcProviderMocked.getBalance.mockReturnValue( BigNumber.from("12000000000000000000") @@ -279,6 +386,19 @@ describe("Credentials library", () => { const result = evaluate(expression) expect(result).toBeTruthy() }) + it("Should sucessfully evaluate a logical expression with the and or not and xor operators in parentheses", () => { + const expression = [ + "true", + "and", + "false", + "or", + "(", + "not", + ")" + ] + const result = evaluate(expression) + expect(result).toBeTruthy() + }) }) }) }) From d0bb60b91190abffc67695bd80e57df93ad915d4 Mon Sep 17 00:00:00 2001 From: Hw <56923450+waddaboo@users.noreply.github.com> Date: Thu, 4 Jul 2024 16:05:25 +0800 Subject: [PATCH 4/6] test(libs-credentials): update githubFollowers import --- libs/credentials/src/index.test.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/libs/credentials/src/index.test.ts b/libs/credentials/src/index.test.ts index fecee620..ba2d2209 100644 --- a/libs/credentials/src/index.test.ts +++ b/libs/credentials/src/index.test.ts @@ -2,6 +2,7 @@ import { BigNumber } from "ethers" import blockchainBalance from "./validators/blockchainBalance" import blockchainTransactions from "./validators/blockchainTransactions" import githubPersonalStars from "./validators/githubPersonalStars" +import githubFollowers from "./validators/githubFollowers" import addProvider from "./addProvider" import addProviders from "./addProviders" import addValidator from "./addValidator" @@ -14,7 +15,7 @@ import { validateCredentials, validateManyCredentials } from "./validateCredentials" -import { githubFollowers, testUtils } from "." +import { testUtils } from "." import { evaluate, tokenize } from "./evaluateExpression" import checkCriteria from "./checkCriteria" import getJsonRpcProvider from "./getJsonRpcProvider" From 24df00b8cf9c184d80acf1089f0eb67e5c56e42c Mon Sep 17 00:00:00 2001 From: Hw <56923450+waddaboo@users.noreply.github.com> Date: Thu, 4 Jul 2024 16:10:04 +0800 Subject: [PATCH 5/6] test(libs-credentials): prettier fix --- libs/credentials/src/index.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libs/credentials/src/index.test.ts b/libs/credentials/src/index.test.ts index ba2d2209..835d76fd 100644 --- a/libs/credentials/src/index.test.ts +++ b/libs/credentials/src/index.test.ts @@ -2,7 +2,7 @@ import { BigNumber } from "ethers" import blockchainBalance from "./validators/blockchainBalance" import blockchainTransactions from "./validators/blockchainTransactions" import githubPersonalStars from "./validators/githubPersonalStars" -import githubFollowers from "./validators/githubFollowers" +import githubFollowers from "./validators/githubFollowers" import addProvider from "./addProvider" import addProviders from "./addProviders" import addValidator from "./addValidator" From 0ee28f3a5c9eebc9212afe7679ffdb2ac6ade47a Mon Sep 17 00:00:00 2001 From: Hw <56923450+waddaboo@users.noreply.github.com> Date: Thu, 4 Jul 2024 16:28:05 +0800 Subject: [PATCH 6/6] test(libs-utils): add more tests to improve test coverage --- libs/utils/src/index.test.ts | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/libs/utils/src/index.test.ts b/libs/utils/src/index.test.ts index 14f656f5..26bc15db 100644 --- a/libs/utils/src/index.test.ts +++ b/libs/utils/src/index.test.ts @@ -1,3 +1,4 @@ +import { blockchainCredentialSupportedNetworks } from "./getSupportedNetworks" import shortenAddress from "./shortenAddress" describe("Utils", () => { @@ -10,4 +11,30 @@ describe("Utils", () => { expect(address).toBe("0x1234...7890") }) }) + + describe("# blockchainCredentialSupportedNetworks", () => { + it("Should return a list of blockchain credential supported network", () => { + const networks = blockchainCredentialSupportedNetworks + + expect(networks).toHaveLength( + blockchainCredentialSupportedNetworks.length + ) + }) + + it("Should return a blockchain credential supported network", () => { + const expected = { + id: "sepolia", + name: "Sepolia" + } + const id = blockchainCredentialSupportedNetworks.find( + (i) => i.id === expected.id + ) + const name = blockchainCredentialSupportedNetworks.find( + (i) => i.name === expected.name + ) + + expect(id).toMatchObject(expected) + expect(name).toMatchObject(expected) + }) + }) })