Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

test(wallet): add functional tests for /blocks and /transactions #825

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion apps/api/src/services/db/transactionsService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ export async function getTransactions(limit: number) {
export async function getTransaction(hash: string): Promise<ApiTransactionResponse | null> {
const tx = await Transaction.findOne({
where: {
hash: hash
hash
},
include: [
{
Expand Down
144 changes: 144 additions & 0 deletions apps/api/test/functional/blocks.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
import { AkashBlock, AkashMessage } from "@akashnetwork/database/dbSchemas/akash";
import { Day, Transaction, Validator } from "@akashnetwork/database/dbSchemas/base";

import { app } from "@src/app";

import { BlockSeeder } from "@test/seeders/block.seeder";
import { MessageSeeder } from "@test/seeders/message.seeder";
import { TransactionSeeder } from "@test/seeders/transaction.seeder";
import { ValidatorSeeder } from "@test/seeders/validator.seeder";

jest.setTimeout(20000);

const getMaxHeight = async () => {
const height = await AkashBlock.max("height");

return (height as number) ?? 0;
};

describe("Blocks", () => {
describe("GET /v1/blocks", () => {
it("resolves list of most recent blocks", async () => {
const response = await app.request("/v1/blocks?limit=2", {
method: "GET",
headers: new Headers({ "Content-Type": "application/json" })
});

expect(response.status).toBe(200);
const blocks = await response.json();
expect(blocks.length).toBe(2);
blocks.forEach((block: unknown) => {
expect(block).toMatchObject({
height: expect.any(Number),
proposer: {
address: expect.any(String),
operatorAddress: expect.any(String),
moniker: expect.any(String),
avatarUrl: expect.toBeTypeOrNull(String)
},
transactionCount: expect.any(Number),
totalTransactionCount: expect.any(Number),
datetime: expect.dateTimeZ()
});
});
});

it("will not resolve more than 100 blocks", async () => {
const response = await app.request("/v1/blocks?limit=101", {
method: "GET",
headers: new Headers({ "Content-Type": "application/json" })
});

expect(response.status).toBe(200);
const blocks = await response.json();
expect(blocks.length).toBe(100);
});
});

describe("GET /v1/blocks/{height}", () => {
it("resolves block by height", async () => {
const maxHeight = await getMaxHeight();
const nextHeight = maxHeight + 1;

const day = await Day.findOne({ order: [["date", "DESC"]] });

const validator = ValidatorSeeder.create();
await Validator.create(validator);

const block = BlockSeeder.create({
height: nextHeight,
proposer: validator.hexAddress,
dayId: day.id
});
await AkashBlock.create(block);

const transaction = TransactionSeeder.create({
height: nextHeight,
hasProcessingError: false
});
await Transaction.create(transaction);

const message = MessageSeeder.create({
txId: transaction.id,
height: nextHeight,
amount: "1000"
});
await AkashMessage.create(message);

const response = await app.request(`/v1/blocks/${block.height}`, {
method: "GET",
headers: new Headers({ "Content-Type": "application/json" })
});

expect(response.status).toBe(200);
const blockLoaded = await response.json();
expect(blockLoaded).toEqual({
height: block.height,
datetime: block.datetime,
proposer: {
address: validator.accountAddress,
operatorAddress: validator.operatorAddress,
moniker: validator.moniker,
avatarUrl: validator.keybaseAvatarUrl
},
hash: blockLoaded.hash,
gasUsed: blockLoaded.gasUsed,
gasWanted: blockLoaded.gasWanted,
transactions: [
{
hash: transaction.hash,
isSuccess: true,
error: null,
fee: parseInt(transaction.fee),
datetime: block.datetime,
messages: [
{
id: message.id,
type: message.type,
amount: parseInt(message.amount)
}
]
}
]
});
});

it("responds 400 for invalid height", async () => {
const response = await app.request("/v1/blocks/a", {
method: "GET",
headers: new Headers({ "Content-Type": "application/json" })
});

expect(response.status).toBe(400);
});

it("responds 404 for a block not found", async () => {
const response = await app.request("/v1/blocks/0", {
method: "GET",
headers: new Headers({ "Content-Type": "application/json" })
});

expect(response.status).toBe(404);
});
});
});
153 changes: 153 additions & 0 deletions apps/api/test/functional/transactions.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
import { AkashBlock, AkashMessage } from "@akashnetwork/database/dbSchemas/akash";
import { Day, Transaction, Validator } from "@akashnetwork/database/dbSchemas/base";
import { get } from "lodash";

import { app } from "@src/app";

import { BlockSeeder } from "@test/seeders/block.seeder";
import { MessageSeeder } from "@test/seeders/message.seeder";
import { TransactionSeeder } from "@test/seeders/transaction.seeder";
import { ValidatorSeeder } from "@test/seeders/validator.seeder";

jest.setTimeout(20000);

const getMaxHeight = async () => {
const height = await AkashBlock.max("height");

return (height as number) ?? 0;
};

describe("Transactions", () => {
describe("GET /v1/transactions", () => {
it("resolves list of most recent transactions", async () => {
const response = await app.request("/v1/transactions?limit=2", {
method: "GET",
headers: new Headers({ "Content-Type": "application/json" })
});

expect(response.status).toBe(200);
const transactions = await response.json();
expect(transactions.length).toBe(2);
transactions.forEach((transaction: unknown) => {
expect(transaction).toMatchObject({
height: expect.any(Number),
datetime: expect.any(String),
hash: expect.any(String),
isSuccess: expect.any(Boolean),
error: expect.toBeTypeOrNull(String),
gasUsed: expect.any(Number),
gasWanted: expect.any(Number),
fee: expect.any(Number),
memo: expect.any(String)
});

get(transaction, "messages", []).forEach((message: unknown) => {
expect(message).toMatchObject({
id: expect.any(String),
type: expect.any(String),
amount: expect.any(Number)
});
});
});
});

it("will not resolve more than 100 transactions", async () => {
const response = await app.request("/v1/transactions?limit=101", {
method: "GET",
headers: new Headers({ "Content-Type": "application/json" })
});

expect(response.status).toBe(200);
const blocks = await response.json();
expect(blocks.length).toBe(100);
});

it("responds 400 when limit is not set", async () => {
const response = await app.request("/v1/transactions", {
method: "GET",
headers: new Headers({ "Content-Type": "application/json" })
});

expect(response.status).toBe(400);
});
});

describe("GET /v1/transactions/{hash}", () => {
it("resolves transaction by hash", async () => {
const maxHeight = await getMaxHeight();
const nextHeight = maxHeight + 1;

const day = await Day.findOne({ order: [["date", "DESC"]] });

const validator = ValidatorSeeder.create();
await Validator.create(validator);

const block = BlockSeeder.create({
height: nextHeight,
proposer: validator.hexAddress,
dayId: day.id
});
await AkashBlock.create(block);

const transaction = TransactionSeeder.create({
height: nextHeight,
hasProcessingError: false
});
await Transaction.create(transaction);

const message = MessageSeeder.create({
txId: transaction.id,
height: nextHeight,
amount: "1000"
});
await AkashMessage.create(message);

const response = await app.request(`/v1/transactions/${transaction.hash}`, {
method: "GET",
headers: new Headers({ "Content-Type": "application/json" })
});

expect(response.status).toBe(200);
const transactionLoaded = await response.json();
expect(transactionLoaded).toEqual({
height: block.height,
datetime: block.datetime,
hash: transaction.hash,
isSuccess: !transaction.hasProcessingError,
multisigThreshold: null,
signers: [],
error: transaction.hasProcessingError ? transaction.log : null,
gasUsed: transaction.gasUsed,
gasWanted: transaction.gasWanted,
fee: parseInt(transaction.fee),
memo: transaction.memo,
messages: [
{
id: message.id,
type: message.type,
data: {
amount: [
{
amount: "10000",
denom: "uakt"
}
],
fromAddress: "akash10ml4dz5npgyhzx3xq0myl44dzycmkgytmc9rhe",
toAddress: "akash1gxglu3ny085vnwearp3kf6tvhqagadyawy05gq"
},
relatedDeploymentId: null
}
]
});
});

it("responds 404 for an unknown hash", async () => {
const response = await app.request("/v1/transactions/unknown-hash", {
method: "GET",
headers: new Headers({ "Content-Type": "application/json" })
});

expect(response.status).toBe(404);
});
});
});
43 changes: 43 additions & 0 deletions apps/api/test/seeders/block.seeder.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import { faker } from "@faker-js/faker";
import { merge } from "lodash";

import { AkashAddressSeeder } from "./akash-address.seeder";

type Block = {
height: number;
datetime: string;
hash: string;
proposer: string;
dayId: string;
txCount: number;
isProcessed: boolean;
totalUAktSpent?: number;
totalUUsdcSpent?: number;
totalUUsdSpent?: number;
activeLeaseCount?: number;
totalLeaseCount?: number;
activeCPU?: number;
activeGPU?: number;
activeMemory?: number;
activeEphemeralStorage?: number;
activePersistentStorage?: number;
activeProviderCount?: number;
};

export class BlockSeeder {
static create(input: Partial<Block> = {}): Block {
return merge(
{
height: faker.number.int({ min: 0, max: 10000000 }),
datetime: faker.date.past().toISOString(),
hash: AkashAddressSeeder.create(),
proposer: AkashAddressSeeder.create(),
dayId: faker.string.uuid(),
txCount: faker.number.int({ min: 0, max: 10000000 }),
isProcessed: faker.datatype.boolean(),
totalTxCount: faker.number.int({ min: 0, max: 10000000 })
},
input
);
}
}
27 changes: 27 additions & 0 deletions apps/api/test/seeders/day.seeder.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { faker } from "@faker-js/faker";
import { merge } from "lodash";

type Day = {
id: string;
date: Date;
aktPrice?: number;
firstBlockHeight: number;
lastBlockHeight?: number;
lastBlockHeightYet: number;
aktPriceChanged: boolean;
};

export class DaySeeder {
static create(input: Partial<Day> = {}): Day {
return merge(
{
id: faker.string.uuid(),
date: faker.date.past(),
firstBlockHeight: faker.number.int({ min: 0, max: 10000000 }),
lastBlockHeightYet: faker.number.int({ min: 0, max: 10000000 }),
aktPriceChanged: faker.datatype.boolean()
},
input
);
}
}
Loading
Loading