diff --git a/.eslintrc b/.eslintrc index d280a1d..37864b9 100644 --- a/.eslintrc +++ b/.eslintrc @@ -5,7 +5,7 @@ }, "parser": "@typescript-eslint/parser", "plugins": ["@typescript-eslint"], - "ignorePatterns": ["node_modules", "dist", "coverage", "jest.config.ts"], + "ignorePatterns": ["node_modules", "dist", "coverage", "jest.config.ts", "jest.config.unit.js"], "extends": ["eslint:recommended", "plugin:@typescript-eslint/recommended", "prettier"] } diff --git a/.gitignore b/.gitignore index 1312a3e..f4b67e0 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,6 @@ node_modules dist .DS_Store +.idea .env \ No newline at end of file diff --git a/.jest/setEnvVars.ts b/.jest/setEnvVars.ts new file mode 100644 index 0000000..93521c6 --- /dev/null +++ b/.jest/setEnvVars.ts @@ -0,0 +1 @@ +process.env.ERGO_NODE_ADDRESS = 'http://localhost'; diff --git a/jest.config.js b/jest.config.js index a440814..e757a47 100644 --- a/jest.config.js +++ b/jest.config.js @@ -1,5 +1,5 @@ module.exports = { - preset: "ts-jest", - testMatch: ["**/__tests__/**/*.[jt]s?(x)", "**/?(*.)+(spec|test).[tj]s?(x)"], - testPathIgnorePatterns: ["/node_modules/", "/dist/"] + preset: "ts-jest", + testMatch: ["**/__tests__/**/*.[jt]s?(x)", "**/?(*.)+(spec|test).[tj]s?(x)"], + testPathIgnorePatterns: ["/node_modules/", "/dist/"] }; diff --git a/jest.config.unit.js b/jest.config.unit.js new file mode 100644 index 0000000..8b3950e --- /dev/null +++ b/jest.config.unit.js @@ -0,0 +1,6 @@ +const baseConfig = require('./jest.config'); + +module.exports = { + ...baseConfig, + setupFiles: ['/.jest/setEnvVars.ts'], +}; diff --git a/package.json b/package.json index d242c99..f854ad7 100644 --- a/package.json +++ b/package.json @@ -5,7 +5,8 @@ "main": "src/index.ts", "scripts": { "dev": "ts-node-dev --transpile-only --no-notify --exit-child src/index.ts", - "test": "jest", + "test:integration": "jest --testPathPattern=tests/integration*", + "test:unit": "jest --config=jest.config.unit.js --testPathIgnorePatterns=tests/integration*", "lint": "eslint . --ext .js,.jsx,.ts,.tsx", "build": "tsc -p tsconfig.json", "start": "node dist/index.js" diff --git a/tests/integration.spec.ts b/tests/integration.spec.ts index e08b16d..433d115 100644 --- a/tests/integration.spec.ts +++ b/tests/integration.spec.ts @@ -19,8 +19,8 @@ const specs: Spec[] = [ { name: "[addresses] balance and transactions count", query: { - query: `query Query($addresses: [String!]!, $atHeight: Int) { - addresses(addresses: $addresses, atHeight: $atHeight) { + query: `query Query($addresses: [String!]!) { + addresses(addresses: $addresses) { transactionsCount balance { nanoErgs @@ -33,8 +33,7 @@ const specs: Spec[] = [ "9hY16vzHmmfyVBwKeFGHvb2bMFsG94A1u7To1QWtUokACyFVENQ", "9fh7mb1w4mFpD9aZDs8atNjnp27xN1HQnsgQk1cRiPaeCWMCfRJ", "9gT3jR5PU9QKrgDuZJ6tKNpoCUwsGPhV6uVg6SL2hmdZGWicq9m" - ], - atHeight: 759893 + ] } }, assert(output) { @@ -60,6 +59,25 @@ const specs: Spec[] = [ } } }, + { + name: "[transactions] no filter", + query: { + query: `query Query($take: Int) { + transactions(take: $take) { + inclusionHeight + transactionId + } + }`, + variables: { + take: 10 + } + }, + assert(output) { + expect(output.errors).toBeUndefined(); + expect(output.data).toBeDefined(); + expect(output.data?.transactions).toHaveLength(10); + } + }, { name: "[transactions] filter by address and max height", query: { @@ -81,22 +99,36 @@ const specs: Spec[] = [ } }, { - name: "[transactions] no filters", + name: "[transactions] filter by headerId", query: { - query: `query Query($take: Int) { - transactions(take: $take) { + query: `query Query($headerId: String) { + transactions(headerId: $headerId) { + headerId + } + }`, + variables: { headerId: "714414fb61d1cd85d08846f7f1debf27b49a616e4a39c1eac55f90200bf25347" } + }, + assert(output) { + expect(output.errors).toBeUndefined(); + expect(output.data).toBeDefined(); + expect(output.data?.transactions).toEqual(expect.arrayContaining([{ headerId: "714414fb61d1cd85d08846f7f1debf27b49a616e4a39c1eac55f90200bf25347" }])); + } + }, + { + name: "[transactions] filter by inclusionHeight", + query: { + query: `query Query($inclusionHeight: Int) { + transactions(inclusionHeight: $inclusionHeight) { inclusionHeight - transactionId } }`, - variables: { - take: 10 - } + variables: { inclusionHeight: 1134187 } }, assert(output) { expect(output.errors).toBeUndefined(); expect(output.data).toBeDefined(); - expect(output.data?.transactions).toHaveLength(10); + expect(output.data?.transactions).toHaveLength(3) + expect(output.data?.transactions).toEqual(expect.arrayContaining([{ inclusionHeight: 1134187 }])); } }, { @@ -166,10 +198,276 @@ const specs: Spec[] = [ ); } } - } + }, + { + name: "[box] filter by boxIds", + query: { + query: `query Query ($boxIds: [String!]){ + boxes(boxIds: $boxIds) { + boxId + } + }`, + variables: { boxIds: [ + "8d13f40194ef5f444c017f3c88a424f90639b9976441e35e7b4fadeb9b5a1e11", + "da2df671ea3392b4ccdc6eb51704a82b4bd1a3aa3f029c69fc420ea3f377edaf" + ]} + }, + assert(output) { + expect(output.errors).toBeUndefined(); + expect(output.data).toBeDefined(); + expect(output.data?.boxes).toHaveLength(2); + expect(output.data?.boxes).toEqual( + expect.arrayContaining([ + { boxId: "8d13f40194ef5f444c017f3c88a424f90639b9976441e35e7b4fadeb9b5a1e11" }, + { boxId: "da2df671ea3392b4ccdc6eb51704a82b4bd1a3aa3f029c69fc420ea3f377edaf" } + ]) + ); + } + }, + { + name: "[box] filter by ergoTreeTemplateHash", + query: { + query: `query Query ($templateHash: String){ + boxes(ergoTreeTemplateHash: $templateHash, take: 10) { + ergoTreeTemplateHash + } + }`, + variables: { templateHash: "961e872f7ab750cb77ad75ea8a32d0ea3472bd0c230de09329b802801b3d1817" } + }, + assert(output) { + expect(output.errors).toBeUndefined(); + expect(output.data).toBeDefined(); + expect(output.data?.boxes).toHaveLength(10); + expect(output.data?.boxes).toEqual(expect.arrayContaining([{ ergoTreeTemplateHash: "961e872f7ab750cb77ad75ea8a32d0ea3472bd0c230de09329b802801b3d1817" }])); + } + }, + { + name: "[box] filter by address", + query: { + query: `query Query ($address: String){ + boxes(address: $address, take: 10) { + address + } + }`, + variables: { address: "88dhgzEuTXaSuf5QC1TJDgdxqJMQEQAM6YaTTRqmUDrmPoVky1b16WAK5zMrq3p2mYqpUNKCyi5CLS9V" } + }, + assert(output) { + expect(output.errors).toBeUndefined(); + expect(output.data).toBeDefined(); + expect(output.data?.boxes).toHaveLength(10); + expect(output.data?.boxes).toEqual(expect.arrayContaining([{ address: "88dhgzEuTXaSuf5QC1TJDgdxqJMQEQAM6YaTTRqmUDrmPoVky1b16WAK5zMrq3p2mYqpUNKCyi5CLS9V" }])); + } + }, + { + name: "[box] filter by txId", + query: { + query: `query Query ($txId: String){ + boxes(transactionId: $txId) { + transactionId + } + }`, + variables: { txId: "6fec163db4752215ba51bf1a0e017380c859575dff3b58d078ce97d9c330e999" } + }, + assert(output) { + expect(output.errors).toBeUndefined(); + expect(output.data).toBeDefined(); + expect(output.data?.boxes).toHaveLength(3); + expect(output.data?.boxes).toEqual(expect.arrayContaining([{ transactionId: "6fec163db4752215ba51bf1a0e017380c859575dff3b58d078ce97d9c330e999" }])); + } + }, + { + name: "[box] filter by registers (R4, needs extra filtering)", + query: { + query: `query Query ($R4: String){ + boxes(registers: {R4: $R4}, spent: true, + ergoTreeTemplateHash: "a4c5968b850cab972092b3593ed5b4c133cba6d4f26df2f274a606c7acffb4a8") { + additionalRegisters + } + }`, + variables: { R4: "1104deb5a9deae01de09b613c0d4f81b" } + }, + assert(output) { + expect(output.errors).toBeUndefined(); + expect(output.data).toBeDefined(); + expect(output.data?.boxes).toEqual(expect.arrayContaining([{ additionalRegisters: { R4: "1104deb5a9deae01de09b613c0d4f81b" } }])); + } + }, + { + name: "[tokens] filter by boxId", + query: { + query: `query Query($boxId: String) { + tokens(boxId: $boxId) { + boxId + } + }`, + variables: { boxId: "089105a867391d773a57d500dab9aef255b0292ec66ce1d9c9813d108d7283e7" } + }, + assert(output) { + expect(output.errors).toBeUndefined(); + expect(output.data).toBeDefined(); + expect(output.data?.tokens).toHaveLength(1); + expect(output.data?.tokens).toEqual([{ boxId: "089105a867391d773a57d500dab9aef255b0292ec66ce1d9c9813d108d7283e7" }]); + } + }, + { + name: "[tokens] filter by tokenName", + query: { + query: `query Query($name: String) { + tokens(name: $name) { + name + } + }`, + variables: { name: "test" } + }, + assert(output) { + expect(output.errors).toBeUndefined(); + expect(output.data).toBeDefined(); + expect(output.data?.tokens).toHaveLength(50); + expect(output.data?.tokens).toEqual(expect.arrayContaining([{ name: "test" }])); + } + }, + { + name: "[inputs] filter by txId", + query: { + query: `query Query($txId: String) { + inputs(transactionId: $txId) { + transactionId + } + }`, + variables: { txId: "dec24fc9c5114d051a08e6a3669f259930d1003fe90ab8ee2e8b04c6ab42ea1c" } + }, + assert(output) { + expect(output.errors).toBeUndefined(); + expect(output.data).toBeDefined(); + expect(output.data?.inputs).toHaveLength(3); + expect(output.data?.inputs).toEqual(expect.arrayContaining([{ transactionId: "dec24fc9c5114d051a08e6a3669f259930d1003fe90ab8ee2e8b04c6ab42ea1c" }])); + } + }, + { + name: "[blockHeaders] filter by parentId", + query: { + query: `query Query($pId: String) { + blockHeaders(parentId: $pId) { + parentId + } + }`, + variables: { pId: "13dbe2b22e60cce05cba4ee2f2d996b2fd0518f1ba8cb4bb04c71efd4207be8a" } + }, + assert(output) { + expect(output.errors).toBeUndefined(); + expect(output.data).toBeDefined(); + expect(output.data?.blockHeaders).toHaveLength(1); + expect(output.data?.blockHeaders).toEqual(expect.arrayContaining([{ parentId: "13dbe2b22e60cce05cba4ee2f2d996b2fd0518f1ba8cb4bb04c71efd4207be8a" }])); + } + }, + { + name: "[blockHeaders] filter by height", + query: { + query: `query Query($height: Int) { + blockHeaders(height: $height) { + height + } + }`, + variables: { height: 300 } + }, + assert(output) { + expect(output.errors).toBeUndefined(); + expect(output.data).toBeDefined(); + expect(output.data?.blockHeaders).toHaveLength(1); + expect(output.data?.blockHeaders).toEqual(expect.arrayContaining([{ height: 300 }])); + } + }, + { + name: "[addresses] fetch two addresses", + query: { + query: `query Query($addresses: [String!]!) { + addresses(addresses: $addresses) { + address + used + } + }`, + variables: { addresses: [ + "9gzA6eZo9HwpjsJBP8ioqo27E5JctkwkE3mBtsRWZVSpBhYExkZ", + "9es6p6rgtF4St2XA2wQ6hgkathnynLv4oxAdNwzZL5LZ6fhPV9s" + ]} + }, + assert(output) { + expect(output.errors).toBeUndefined(); + expect(output.data).toBeDefined(); + expect(output.data?.addresses).toHaveLength(2); + expect(output.data?.addresses).toEqual([ + { address: "9gzA6eZo9HwpjsJBP8ioqo27E5JctkwkE3mBtsRWZVSpBhYExkZ", used: true }, + { address: "9es6p6rgtF4St2XA2wQ6hgkathnynLv4oxAdNwzZL5LZ6fhPV9s", used: true } + ]); + } + }, + { + name: "[mempool] simple mempool fetch", + query: { + query: `query Query { + mempool { + size + } + }` + }, + assert(output) { + expect(output.errors).toBeUndefined(); + expect(output.data).toBeDefined(); + expect(output.data?.mempool.size).toBeDefined(); + } + }, + { + name: "[blocks] filter by height", + query: { + query: `query Query($height: Int) { + blocks(height: $height) { + height + } + }`, + variables: { height: 300 } + }, + assert(output) { + expect(output.errors).toBeUndefined(); + expect(output.data).toBeDefined(); + expect(output.data?.blocks).toHaveLength(1); + expect(output.data?.blocks).toEqual([{ height: 300 }]); + } + }, + { + name: "[blocks] filter by headerId", + query: { + query: `query Query($headerId: String) { + blocks(headerId: $headerId) { + headerId + } + }`, + variables: { headerId: "6ba802b17c9598a15c8da1736e975e34143e93d799f5d2a9bc408bd2b3f19a1f" } + }, + assert(output) { + expect(output.errors).toBeUndefined(); + expect(output.data).toBeDefined(); + expect(output.data?.blocks).toHaveLength(1); + expect(output.data?.blocks).toEqual([{ headerId: "6ba802b17c9598a15c8da1736e975e34143e93d799f5d2a9bc408bd2b3f19a1f" }]); + } + }, + { + name: "[state] simple state fetch", + query: { + query: `query Query { + state { + network + } + }` + }, + assert(output) { + expect(output.errors).toBeUndefined(); + expect(output.data).toBeDefined(); + expect(output.data?.state.network).toBe("mainnet"); + } + }, ]; -describe("integration tests", () => { +describe("Integration Tests", () => { let server!: ApolloServer; let dataSource!: DataSource; diff --git a/tests/services/node-service.spec.ts b/tests/services/node-service.spec.ts new file mode 100644 index 0000000..8b0c2de --- /dev/null +++ b/tests/services/node-service.spec.ts @@ -0,0 +1,51 @@ +import axios from "axios"; +import { nodeService } from '../../src/services/node-service'; + +jest.mock('axios'); + +describe('NodeService', () => { + afterEach(() => { + jest.resetAllMocks(); + }); + + /** + * @description Test if the checkUrl runs without throwing an error + * @expected Should not throw an error + */ + it('Should not return error while checking URL', () => { + expect(() => { + nodeService.checkUrl(); + }).not.toThrowError(); + }); + + /** + * @description Run getNodeInfo to feth the info from the node + * @expected Should return nodeInfo + */ + it('Should return nodeInfo', async() => { + axios.get = jest.fn().mockResolvedValue({ nodeInfo: 1 }); + const nodeInfo = await nodeService.getNodeInfo(); + expect(nodeInfo).toEqual({ nodeInfo: 1 }); + }); + + /** + * @description Run checkTransaction to check the transaction + * @expected Should return transaction + */ + it('Should return transaction', async() => { + axios.post = jest.fn().mockResolvedValue({ transaction: 1 }); + const transaction = await nodeService.checkTransaction(null as any); + expect(transaction).toEqual({ transaction: 1 }); + }); + + /** + * @description Run submitTransaction to submit the transaction + * @expected Should return transaction + */ + it('Should return transaction', async() => { + axios.post = jest.fn().mockResolvedValue({ transaction: 1 }); + const transaction = await nodeService.submitTransaction(null as any); + expect(transaction).toEqual({ transaction: 1 }); + }); + +}); diff --git a/tests/utils.spec.ts b/tests/utils.spec.ts new file mode 100644 index 0000000..0f9ac7f --- /dev/null +++ b/tests/utils.spec.ts @@ -0,0 +1,17 @@ +import { removeUndefined } from "../src/utils"; + +describe("utils", () => { + describe("removeUndefined", () => { + it("should remove undefined values", () => { + const result = removeUndefined({ + a: 1, + b: undefined, + c: null + }); + + expect(result).toEqual({ + a: 1 + }); + }); + }); +});