Skip to content

Commit

Permalink
Remove chains import for getBlock query (#50)
Browse files Browse the repository at this point in the history
The import of the `chains` variable from `fetch/openapi.ts` caused
a DB query to be executed once the module was loaded. This behavior
was also preventing testing any function inside the `queries.ts` module.

The fix makes the `getBlock` function explicitly query the DB for
supported chains (as they might also get updated over time). The
function is mocked in the tests using the latest Bun feature (1.0.8+)
of *mock modules* in order to allow "offline" testing.
  • Loading branch information
0237h authored Nov 3, 2023
1 parent f755963 commit 66152da
Show file tree
Hide file tree
Showing 4 changed files with 59 additions and 50 deletions.
Binary file modified bun.lockb
Binary file not shown.
2 changes: 1 addition & 1 deletion src/fetch/openapi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ const TAGS = {
DOCS: "Documentation",
} as const;

export const chains = await supportedChainsQuery();
const chains = await supportedChainsQuery();
const block_example = (await makeQuery(await getBlock( new URLSearchParams({limit: "2"})))).data;

const timestampSchema: SchemaObject = { anyOf: [
Expand Down
27 changes: 18 additions & 9 deletions src/queries.spec.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,26 @@
import { expect, test } from "bun:test";
import { getBlock, getChain } from "./queries.js";
import { chains } from './fetch/openapi.js';
import { expect, jest, mock, test } from "bun:test";
import { createBlockQuery, getBlock, getChain } from "./queries.js";
import { supportedChainsQuery } from "./fetch/chains.js";

test("getBlock", () => {
expect(getBlock(new URLSearchParams({ chain: "eth", block_number: "123" })))
// Mock supported chains data to prevent DB query
mock.module("./fetch/chains.ts", () => ({ supportedChainsQuery: jest.fn().mockResolvedValue(["eth", "polygon"]) }));

test("createBlockQuery", () => {
expect(createBlockQuery(new URLSearchParams({ chain: "eth", block_number: "123" })))
.toBe(`SELECT * FROM blocks WHERE (chain == 'eth' AND block_number == '123') ORDER BY block_number DESC LIMIT 1`);

expect(getBlock(new URLSearchParams({ chain: "eth", greater_or_equals_by_timestamp: '1438270048', less_or_equals_by_timestamp: '1438270083', limit: '3' })))
expect(createBlockQuery(new URLSearchParams({ chain: "eth", greater_or_equals_by_timestamp: '1438270048', less_or_equals_by_timestamp: '1438270083', limit: '3' })))
.toBe(`SELECT * FROM blocks WHERE (toUnixTimestamp(timestamp) >= 1438270048 AND toUnixTimestamp(timestamp) <= 1438270083 AND chain == 'eth') ORDER BY block_number DESC LIMIT 3`);
});

test("getBlock", async () => {
const singleChainQuery = new URLSearchParams({ chain: "eth", block_number: "123" });
expect(getBlock(singleChainQuery)).resolves.toBe(createBlockQuery(singleChainQuery));

// Check that if not chain parameter is passed, all chains are included in the selection
chains.forEach((chain) => {
expect(getBlock(new URLSearchParams({ block_number: "123" })))
// Check that if no chain parameter is passed, all chains are included in the selection
let supportedChains = await supportedChainsQuery();
supportedChains.forEach((chain) => {
expect(getBlock(new URLSearchParams({ block_number: "123" }))).resolves
.toContain(`SELECT * FROM blocks WHERE (chain == '${chain}' AND block_number == '123') ORDER BY block_number DESC LIMIT 1`);
});
});
Expand Down
80 changes: 40 additions & 40 deletions src/queries.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { DEFAULT_SORT_BY, config } from './config.js';
import { config } from './config.js';
import { parseBlockId, parseBlockNumber, parseChain, parseLimit, parseSortBy, parseTimestamp } from './utils.js';
import { chains } from './fetch/openapi.js';
import { supportedChainsQuery } from './fetch/chains.js';

export interface Block {
block_number: number;
Expand All @@ -9,54 +9,54 @@ export interface Block {
chain: string;
}

export function getBlock(searchParams: URLSearchParams) {
// TO-DO: Modulo block number (ex: search by every 1M blocks)

let createBlockQuery = (searchParams: URLSearchParams) => {
// SQL Query
let query = `SELECT * FROM ${config.table}`;
const where = [];
export function createBlockQuery (searchParams: URLSearchParams) {
// SQL Query
let query = `SELECT * FROM ${config.table}`;
const where = [];

// Clickhouse Operators
// https://clickhouse.com/docs/en/sql-reference/operators
const operators = [
["greater_or_equals", ">="],
["greater", ">"],
["less_or_equals", "<="],
["less", "<"],
// TO-DO: Modulo block number (ex: search by every 1M blocks)
// Clickhouse Operators
// https://clickhouse.com/docs/en/sql-reference/operators
const operators = [
["greater_or_equals", ">="],
["greater", ">"],
["less_or_equals", "<="],
["less", "<"],
]
for ( const [key, operator] of operators ) {
const block_number = parseBlockNumber(searchParams.get(`${key}_by_block_number`));
const timestamp = parseTimestamp(searchParams.get(`${key}_by_timestamp`));
if (block_number) where.push(`block_number ${operator} ${block_number}`);
if (timestamp) where.push(`toUnixTimestamp(timestamp) ${operator} ${timestamp}`);
}
for ( const [key, operator] of operators ) {
const block_number = parseBlockNumber(searchParams.get(`${key}_by_block_number`));
const timestamp = parseTimestamp(searchParams.get(`${key}_by_timestamp`));
if (block_number) where.push(`block_number ${operator} ${block_number}`);
if (timestamp) where.push(`toUnixTimestamp(timestamp) ${operator} ${timestamp}`);
}

// equals
const chain = parseChain(searchParams.get("chain"));
const block_id = parseBlockId(searchParams.get("block_id"));
const block_number = parseBlockNumber(searchParams.get('block_number'));
const timestamp = parseTimestamp(searchParams.get('timestamp'));
if (chain) where.push(`chain == '${chain}'`);
if (block_id) where.push(`block_id == '${block_id}'`);
if (block_number) where.push(`block_number == '${block_number}'`);
if (timestamp) where.push(`toUnixTimestamp(timestamp) == ${timestamp}`);
// equals
const chain = parseChain(searchParams.get("chain"));
const block_id = parseBlockId(searchParams.get("block_id"));
const block_number = parseBlockNumber(searchParams.get('block_number'));
const timestamp = parseTimestamp(searchParams.get('timestamp'));
if (chain) where.push(`chain == '${chain}'`);
if (block_id) where.push(`block_id == '${block_id}'`);
if (block_number) where.push(`block_number == '${block_number}'`);
if (timestamp) where.push(`toUnixTimestamp(timestamp) == ${timestamp}`);

// Join WHERE statements with AND
if ( where.length ) query += ` WHERE (${where.join(' AND ')})`;
// Join WHERE statements with AND
if ( where.length ) query += ` WHERE (${where.join(' AND ')})`;

// Sort and Limit
const limit = parseLimit(searchParams.get("limit"));
const sort_by = parseSortBy(searchParams.get("sort_by"));
query += ` ORDER BY block_number ${sort_by}`
query += ` LIMIT ${limit}`
// Sort and Limit
const limit = parseLimit(searchParams.get("limit"));
const sort_by = parseSortBy(searchParams.get("sort_by"));
query += ` ORDER BY block_number ${sort_by}`
query += ` LIMIT ${limit}`

return query;
};
return query;
};

export async function getBlock(searchParams: URLSearchParams) {
const chain = searchParams.get("chain");

if (!chain) {
const chains = await supportedChainsQuery();
let queries = chains.map((chain) => {
searchParams.set('chain', chain);
return createBlockQuery(searchParams);
Expand Down

0 comments on commit 66152da

Please sign in to comment.