Skip to content

Commit

Permalink
chore(tests): add tests for blockheight and sortkey, break tests into…
Browse files Browse the repository at this point in the history
… domain centric files
  • Loading branch information
atticusofsparta committed Feb 29, 2024
1 parent ecb279d commit f094f15
Show file tree
Hide file tree
Showing 12 changed files with 374 additions and 73 deletions.
47 changes: 35 additions & 12 deletions src/common/caches/arns-remote-cache.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import {
ArNSStateResponse,
Gateway,
HTTPClient,
ReadInteractionFilters,
} from '../../types/index.js';
import { NotFound } from '../error.js';
import { AxiosHTTPService } from '../http.js';
Expand Down Expand Up @@ -58,32 +59,44 @@ export class ArNSRemoteCache implements ArIOContract {
}
}

async getGateway({ address }: { address: string }) {
async getGateway({
address,
blockHeight,
sortKey,
}: { address: string } & ReadInteractionFilters) {
this.logger.debug(`Fetching gateway ${address}`);
const gateway = await this.getGateways().then((gateways) => {
if (gateways[address] === undefined) {
throw new NotFound(`Gateway not found: ${address}`);
}
return gateways[address];
});
const gateway = await this.getGateways({ blockHeight, sortKey }).then(
(gateways) => {
if (gateways[address] === undefined) {
throw new NotFound(`Gateway not found: ${address}`);
}
return gateways[address];
},
);
return gateway;
}

async getGateways() {
async getGateways({ blockHeight, sortKey }: ReadInteractionFilters = {}) {
this.logger.debug(`Fetching gateways`);
const { result } = await this.http.get<
ArNSStateResponse<'result', Record<string, Gateway>>
>({
endpoint: `/contract/${this.contractTxId.toString()}/read/gateways`,
params: { blockHeight, sortKey: sortKey?.toString() },
});
return result;
}

async getBalance({ address }: { address: string }) {
async getBalance({
address,
blockHeight,
sortKey,
}: { address: string } & ReadInteractionFilters) {
this.logger.debug(`Fetching balance for ${address}`);
const { result } = await this.http
.get<ArNSStateResponse<'result', number>>({
endpoint: `/contract/${this.contractTxId.toString()}/state/balances/${address}`,
params: { blockHeight, sortKey: sortKey?.toString() },
})
.catch((e) => {
if (e instanceof NotFound) {
Expand All @@ -94,32 +107,42 @@ export class ArNSRemoteCache implements ArIOContract {
return result;
}

async getBalances() {
async getBalances({ blockHeight, sortKey }: ReadInteractionFilters = {}) {
this.logger.debug(`Fetching balances`);
const { result } = await this.http.get<
ArNSStateResponse<'result', Record<string, number>>
>({
endpoint: `/contract/${this.contractTxId.toString()}/state/balances`,
params: { blockHeight, sortKey: sortKey?.toString() },
});
return result;
}

async getRecord({ domain }: { domain: string }): Promise<ArNSNameData> {
async getRecord({
domain,
blockHeight,
sortKey,
}: { domain: string } & ReadInteractionFilters): Promise<ArNSNameData> {
this.logger.debug(`Fetching record for ${domain}`);
const { result } = await this.http.get<
ArNSStateResponse<'result', ArNSNameData>
>({
endpoint: `/contract/${this.contractTxId.toString()}/state/records/${domain}`,
params: { blockHeight, sortKey: sortKey?.toString() },
});
return result;
}

async getRecords(): Promise<Record<string, ArNSNameData>> {
async getRecords({
blockHeight,
sortKey,
}: ReadInteractionFilters = {}): Promise<Record<string, ArNSNameData>> {
this.logger.debug(`Fetching all records`);
const { result } = await this.http.get<
ArNSStateResponse<'result', Record<string, ArNSNameData>>
>({
endpoint: `/contract/${this.contractTxId.toString()}/state/records`,
params: { blockHeight, sortKey: sortKey?.toString() },
});
return result;
}
Expand Down
3 changes: 3 additions & 0 deletions src/common/http.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,16 +39,19 @@ export class AxiosHTTPService implements HTTPClient {
signal,
allowedStatuses = [200, 202],
headers,
params,
}: {
endpoint: string;
signal?: AbortSignal;
allowedStatuses?: number[];
headers?: Record<string, string>;
params?: Record<string, any>; // eslint-disable-line @typescript-eslint/no-explicit-any
}): Promise<T> {
this.logger.debug(`Get request to endpoint: ${endpoint}`);
const { status, statusText, data } = await this.axios.get<T>(endpoint, {
headers,
signal,
params,
});

if (!allowedStatuses.includes(status)) {
Expand Down
4 changes: 4 additions & 0 deletions src/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,10 @@
*/

export const ARWEAVE_TX_REGEX = new RegExp('^[a-zA-Z0-9_-]{43}$');
// sortkey: padded blockheight to 12, JS timestamp, hash of transactionID + block hash. Timestamp only applicable to L2 and normally is all zeros.
export const SORT_KEY_REGEX = new RegExp(
'^[0-9]{12},[0-9]{13},[a-fA-F0-9]{64}$',
);
export const ARNS_TESTNET_REGISTRY_TX =
process.env.ARNS_REGISTRY_TX ?? 'bLAgYxAdX2Ry-nt6aH2ixgvJXbpsEYm28NgJgyqfs-U';

Expand Down
27 changes: 23 additions & 4 deletions src/types/common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,15 +14,32 @@
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import { SmartWeaveSortKey } from '../utils/index.js';
import { ArNSNameData, Gateway } from './contract-state.js';

export type EvaluationFilters = {
blockHeight?: number;
sortKey?: SmartWeaveSortKey; // should be tested against regex for validity
};

// TODO: extend type with other read filters (e.g max eval time)
export type ReadInteractionFilters = EvaluationFilters;

// TODO: extend with additional methods
export interface ArIOContract {
getGateway({ address }: { address: WalletAddress }): Promise<Gateway>;
getGateway(
props: { address: WalletAddress } & ReadInteractionFilters,
): Promise<Gateway>;
getGateways(): Promise<Record<WalletAddress, Gateway>>;
getBalance({ address }: { address: WalletAddress }): Promise<number>;
getBalances(): Promise<Record<WalletAddress, number>>;
getRecord({ domain }: { domain: string }): Promise<ArNSNameData>;
getBalance(
props: { address: WalletAddress } & ReadInteractionFilters,
): Promise<number>;
getBalances(
props: ReadInteractionFilters,
): Promise<Record<WalletAddress, number>>;
getRecord(
props: { domain: string } & ReadInteractionFilters,
): Promise<ArNSNameData>;
getRecords(): Promise<Record<string, ArNSNameData>>;
}

Expand All @@ -45,11 +62,13 @@ export interface HTTPClient {
signal,
headers,
allowedStatuses,
params,
}: {
endpoint: string;
signal?: AbortSignal;
headers?: Record<string, string>;
allowedStatuses?: number[];
params?: Record<string, any>; // eslint-disable-line @typescript-eslint/no-explicit-any
}): Promise<T>;
// TODO: add post method
// post<T>({
Expand Down
1 change: 1 addition & 0 deletions src/utils/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,3 +16,4 @@
*/
export * from './arweave.js';
export * from './http-client.js';
export * from './smartweave/index.js';
13 changes: 13 additions & 0 deletions src/utils/smartweave/evaluation.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { SmartWeaveSortKey } from './evaluation.js';

describe(`Smartweave eval utils`, () => {
it(`should throw on a bad sort key`, async () => {
const sortKey = '123,456,abc';
const error = await (async () => new SmartWeaveSortKey(sortKey))().catch(
(e) => e,
);

expect(error).toBeInstanceOf(Error);
expect(error.message).toContain(sortKey);
});
});
49 changes: 49 additions & 0 deletions src/utils/smartweave/evaluation.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
/**
* Copyright (C) 2022-2024 Permanent Data Solutions, Inc. All Rights Reserved.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import { SORT_KEY_REGEX } from '../../constants.js';

export class SmartWeaveSortKey {
private _sortKey: string;
constructor(sortKey: string) {
if (!SmartWeaveSortKey.validate(sortKey)) {
throw new Error(`Invalid sort key: ${sortKey}`);
}

this._sortKey = sortKey;
}

static validate(sortKey: string): boolean {
return SORT_KEY_REGEX.test(sortKey);
}

toString(): string {
return this._sortKey;
}

parts(): string[] {
return this._sortKey.split(',');
}
blockHeight(): number {
return parseInt(this.parts()[0]);
}
timestamp(): number {
return parseInt(this.parts()[1]);
}
hash(): string {
return this.parts()[2];
}
}
17 changes: 17 additions & 0 deletions src/utils/smartweave/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
/**
* Copyright (C) 2022-2024 Permanent Data Solutions, Inc. All Rights Reserved.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
export * from './evaluation.js';
57 changes: 0 additions & 57 deletions tests/arns-remote-cache.test.ts

This file was deleted.

Loading

0 comments on commit f094f15

Please sign in to comment.