Skip to content

Commit

Permalink
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
add tests
Browse files Browse the repository at this point in the history
arobsn committed Nov 6, 2023
1 parent acd0073 commit e235681
Showing 7 changed files with 3,450 additions and 153 deletions.
1 change: 1 addition & 0 deletions packages/_test-vectors/index.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
export * from "./mockedBoxes";
export * from "./mockedTransactions";
export * from "./mockedGraphQLResponses";
3,084 changes: 3,084 additions & 0 deletions packages/_test-vectors/mockedGraphQLResponses.ts

Large diffs are not rendered by default.

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -18,15 +18,15 @@ import {
TransactionEvaluationResult,
TransactionReductionResult
} from "@fleet-sdk/common";
import { some } from "packages/common/src";
import { orderBy, some, uniqBy } from "packages/common/src";
import { createGqlOperation, GraphQLRequestOptions, isRequestParam } from "../utils";
import {
ALL_BOX_QUERY,
ALL_BOXES_QUERY,
CHECK_TX_MUTATION,
CONF_BOX_QUERY,
CONF_BOXES_QUERY,
HEADERS_QUERY,
SEND_TX_MUTATION,
UNCONF_BOX_QUERY
UNCONF_BOXES_QUERY
} from "./queries";

export type GraphQLBoxWhere = BoxWhere & {
@@ -65,9 +65,9 @@ export class ErgoGraphQLProvider implements IChainDataProvider<BoxWhere> {
{ throwOnNonNetworkError: true }
);

this.#getConfBoxes = createGqlOperation<ConfBoxesResp, BoxesArgs>(CONF_BOX_QUERY, opt);
this.#getUnconfBoxes = createGqlOperation<UnconfBoxesResp, BoxesArgs>(UNCONF_BOX_QUERY, opt);
this.#getAllBoxes = createGqlOperation<AllBoxesResp, BoxesArgs>(ALL_BOX_QUERY, opt);
this.#getConfBoxes = createGqlOperation<ConfBoxesResp, BoxesArgs>(CONF_BOXES_QUERY, opt);
this.#getUnconfBoxes = createGqlOperation<UnconfBoxesResp, BoxesArgs>(UNCONF_BOXES_QUERY, opt);
this.#getAllBoxes = createGqlOperation<AllBoxesResp, BoxesArgs>(ALL_BOXES_QUERY, opt);
this.#getHeaders = createGqlOperation<HeadersResp, HeadersArgs>(HEADERS_QUERY, opt);
this.#checkTx = createGqlOperation<CheckTxResp, SignedTxArgsResp>(CHECK_TX_MUTATION, opt);
this.#sendTx = createGqlOperation<SendTxResp, SignedTxArgsResp>(SEND_TX_MUTATION, opt);
@@ -76,82 +76,73 @@ export class ErgoGraphQLProvider implements IChainDataProvider<BoxWhere> {
#fetchBoxes(args: BoxesArgs, inclConf: boolean, inclUnconf: boolean) {
if (inclConf && inclUnconf) {
return this.#getAllBoxes(args);
} else if (inclConf) {
return this.#getConfBoxes(args);
} else if (inclUnconf) {
return this.#getUnconfBoxes(args);
} else {
return this.#getConfBoxes(args);
}

return;
}

async *streamUnspentBoxes(query: GraphQLBoxQuery): AsyncGenerator<ChainClientBox[]> {
if (isEmpty(query.where)) {
throw new Error("Cannot fetch unspent boxes without a where clause.");
}

const includeMempool = query.includeUnconfirmed || query.includeUnconfirmed === undefined;
const notBeingSpent = (box: Box) => !box.beingSpent;
const returnedBoxIds = new Set<string>();
const includeMempool = query.includeUnconfirmed !== false;
const { where } = query;
const queryArgs = {
spent: false,
boxIds: query.where.boxIds ?? (query.where.boxId ? [query.where.boxId] : undefined),
ergoTrees:
query.where.contracts ?? (query.where.contract ? [query.where.contract] : undefined),
ergoTreeTemplateHash: query.where.template,
tokenId: query.where.tokenId,
boxIds: where.boxIds ?? (where.boxId ? [where.boxId] : undefined),
ergoTrees: where.contracts ?? (where.contract ? [where.contract] : undefined),
ergoTreeTemplateHash: where.template,
tokenId: where.tokenId,
skip: 0,
take: PAGE_SIZE
} satisfies BoxesArgs;

const notBeingSpent = (box: Box) => !box.beingSpent;
const returnedBoxIds = new Set<string>();
let fetchConfirmed = true;
let fetchMempool = includeMempool;

do {
const response = await this.#fetchBoxes(queryArgs, fetchConfirmed, fetchMempool);
if (!response) break;

const { data } = response;
let boxes: ChainClientBox[] = [];

if (hasMempool(data)) {
if (some(data.mempool.boxes)) {
const mempoolBoxes = data.mempool.boxes
.filter(notBeingSpent)
.map(mapBoxAsConfirmed(false));

boxes = boxes.concat(mempoolBoxes);
}

fetchMempool = data.mempool.boxes.length === PAGE_SIZE;
}

if (hasConfirmed(data)) {
if (some(data.boxes)) {
const confirmedBoxes = (
includeMempool ? data.boxes.filter(notBeingSpent) : data.boxes
).map(mapBoxAsConfirmed(true));
).map(asConfirmed(true));

boxes = boxes.concat(confirmedBoxes);
}

fetchConfirmed = data.boxes.length === PAGE_SIZE;
}

if (includeMempool && hasMempool(data)) {
if (some(data.mempool.boxes)) {
const mempoolBoxes = data.mempool.boxes.filter(notBeingSpent).map(asConfirmed(false));
boxes = boxes.concat(mempoolBoxes);
}

fetchMempool = data.mempool.boxes.length === PAGE_SIZE;
}

if (some(boxes)) {
// boxes can be moved from mempool to the blockchain while streaming,
// boxes can be moved from the mempool to the blockchain while streaming,
// so we need to filter out boxes that have already been returned.
let returnedSome = false;
for (const box of boxes) {
if (!returnedSome && returnedBoxIds.has(box.boxId)) {
returnedSome = true;
} else {
returnedBoxIds.add(box.boxId);
}
if (boxes.some((box) => returnedBoxIds.has(box.boxId))) {
boxes = boxes.filter((b) => !returnedBoxIds.has(b.boxId));
}

boxes = returnedSome ? boxes.filter((b) => !returnedBoxIds.has(b.boxId)) : boxes;
if (some(boxes)) {
boxes = uniqBy(boxes, (box) => box.boxId);
boxes.forEach((box) => returnedBoxIds.add(box.boxId));

yield boxes;
}
}
@@ -166,7 +157,7 @@ export class ErgoGraphQLProvider implements IChainDataProvider<BoxWhere> {
boxes = boxes.concat(chunk);
}

return boxes;
return orderBy(boxes, (box) => box.creationHeight);
}

async getLastHeaders(count: number): Promise<BlockHeader[]> {
@@ -213,14 +204,14 @@ export class ErgoGraphQLProvider implements IChainDataProvider<BoxWhere> {
}

function hasMempool(data: AllBoxesResp | ConfBoxesResp | UnconfBoxesResp): data is UnconfBoxesResp {
return !!(data as UnconfBoxesResp).mempool;
return !!(data as UnconfBoxesResp)?.mempool?.boxes;
}

function hasConfirmed(data: AllBoxesResp | ConfBoxesResp | UnconfBoxesResp): data is ConfBoxesResp {
return !!(data as ConfBoxesResp).boxes;
return !!(data as ConfBoxesResp)?.boxes;
}

function mapBoxAsConfirmed(confirmed: boolean) {
function asConfirmed(confirmed: boolean) {
return (box: Box): ChainClientBox => ({
...box,
value: BigInt(box.value),
Original file line number Diff line number Diff line change
@@ -1,21 +1,21 @@
import { describe, expect, it } from "vitest";
import { ALL_BOX_QUERY, CONF_BOX_QUERY, UNCONF_BOX_QUERY } from "./queries";
import { ALL_BOXES_QUERY, CONF_BOXES_QUERY, UNCONF_BOXES_QUERY } from "./queries";

describe("Box queries", () => {
it("Should build confirmed box query", () => {
expect(CONF_BOX_QUERY).to.be.equal(
expect(CONF_BOXES_QUERY).to.be.equal(
`query boxes($spent: Boolean! $boxIds: [String!] $ergoTrees: [String!] $ergoTreeTemplateHash: String $tokenId: String $skip: Int $take: Int) { boxes(spent: $spent boxIds: $boxIds ergoTrees: $ergoTrees ergoTreeTemplateHash: $ergoTreeTemplateHash tokenId: $tokenId skip: $skip take: $take) { boxId transactionId index value creationHeight ergoTree assets { tokenId amount } additionalRegisters beingSpent } }`
);
});

it("Should build confirmed + unconfirmed box query", () => {
expect(ALL_BOX_QUERY).to.be.equal(
expect(ALL_BOXES_QUERY).to.be.equal(
`query boxes($spent: Boolean! $boxIds: [String!] $ergoTrees: [String!] $ergoTreeTemplateHash: String $tokenId: String $skip: Int $take: Int) { boxes(spent: $spent boxIds: $boxIds ergoTrees: $ergoTrees ergoTreeTemplateHash: $ergoTreeTemplateHash tokenId: $tokenId skip: $skip take: $take) { boxId transactionId index value creationHeight ergoTree assets { tokenId amount } additionalRegisters beingSpent } mempool { boxes(boxIds: $boxIds ergoTrees: $ergoTrees ergoTreeTemplateHash: $ergoTreeTemplateHash tokenId: $tokenId skip: $skip take: $take) { boxId transactionId index value creationHeight ergoTree assets { tokenId amount } additionalRegisters beingSpent } } }`
);
});

it("Should build unconfirmed box query", () => {
expect(UNCONF_BOX_QUERY).to.be.equal(
expect(UNCONF_BOXES_QUERY).to.be.equal(
`query boxes($spent: Boolean! $boxIds: [String!] $ergoTrees: [String!] $ergoTreeTemplateHash: String $tokenId: String $skip: Int $take: Int) { mempool { boxes(boxIds: $boxIds ergoTrees: $ergoTrees ergoTreeTemplateHash: $ergoTreeTemplateHash tokenId: $tokenId skip: $skip take: $take) { boxId transactionId index value creationHeight ergoTree assets { tokenId amount } additionalRegisters beingSpent } } }`
);
});
6 changes: 3 additions & 3 deletions packages/blockchain-providers/src/ergo-graphql/queries.ts
Original file line number Diff line number Diff line change
@@ -4,9 +4,9 @@ const B = [
`boxId transactionId index value creationHeight ergoTree assets { tokenId amount } additionalRegisters beingSpent`
];

export const CONF_BOX_QUERY = `${B[0]} { boxes(spent: $spent ${B[1]}) { ${B[2]} } }`;
export const UNCONF_BOX_QUERY = `${B[0]} { mempool { boxes(${B[1]}) { ${B[2]} } } }`;
export const ALL_BOX_QUERY = `${B[0]} { boxes(spent: $spent ${B[1]}) { ${B[2]} } mempool { boxes(${B[1]}) { ${B[2]} } } }`;
export const CONF_BOXES_QUERY = `${B[0]} { boxes(spent: $spent ${B[1]}) { ${B[2]} } }`;
export const UNCONF_BOXES_QUERY = `${B[0]} { mempool { boxes(${B[1]}) { ${B[2]} } } }`;
export const ALL_BOXES_QUERY = `${B[0]} { boxes(spent: $spent ${B[1]}) { ${B[2]} } mempool { boxes(${B[1]}) { ${B[2]} } } }`;
export const HEADERS_QUERY = `query blockHeaders($take: Int) { blockHeaders(take: $take) {headerId timestamp version adProofsRoot stateRoot transactionsRoot nBits extensionHash powSolutions height difficulty parentId votes } }`;
export const CHECK_TX_MUTATION = `mutation checkTransaction($signedTransaction: SignedTransaction!) { checkTransaction(signedTransaction: $signedTransaction) }`;
export const SEND_TX_MUTATION = `mutation submitTransaction($signedTransaction: SignedTransaction!) { submitTransaction(signedTransaction: $signedTransaction) }`;
5 changes: 5 additions & 0 deletions packages/blockchain-providers/src/utils/_tests.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
export const mockResponse = (data: string) => {
return { text: () => new Promise((resolve) => resolve(data)) } as unknown as Response;
};

export const mockChunkedResponse = (chunks: string[]) => {
let i = 0;
return { text: () => new Promise((resolve) => resolve(chunks[i++])) } as unknown as Response;
};

0 comments on commit e235681

Please sign in to comment.