Skip to content

Commit

Permalink
chore(tests): add unit tests to contract functions
Browse files Browse the repository at this point in the history
  • Loading branch information
atticusofsparta committed Dec 12, 2023
1 parent 441cf11 commit 8637271
Show file tree
Hide file tree
Showing 21 changed files with 1,665 additions and 67 deletions.
16 changes: 16 additions & 0 deletions globals.d.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,18 @@
/**
* 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/>.
*/
declare let SmartWeave: any;
declare let ContractError: any;
12 changes: 6 additions & 6 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
},
"devDependencies": {
"@babel/preset-typescript": "^7.16.7",
"@commitlint/config-conventional": "^18.4.3",
"@trivago/prettier-plugin-sort-imports": "^4.3.0",
"@types/jest": "^27.4.0",
"@types/node": "^20.10.3",
Expand All @@ -26,6 +27,7 @@
"arbundles": "^0.6.18",
"arlocal": "1.1.60",
"arweave": "1.13.7",
"commitlint": "^18.4.3",
"dotenv": "^16.3.1",
"esbuild": "^0.17.12",
"eslint": "^6.7.2",
Expand All @@ -35,7 +37,8 @@
"eslint-plugin-prettier": "^3.3.1",
"husky": "^8.0.3",
"jest": "^29.7.0",
"prettier": "^3.1.0",
"lint-staged": "^15.2.0",
"prettier": "^3.1.1",
"replace-in-file": "^6.2.0",
"rimraf": "^5.0.5",
"ts-jest": "^29.1.1",
Expand All @@ -46,13 +49,10 @@
},
"lint-staged": {
"**/*.{ts,js,json}": [
"eslint --fix .",
"prettier --write ."
"eslint . --fix",
"prettier . --write"
]
},
"dependencies": {
"lint-staged": "^15.2.0"
},
"resolutions": {
"arweave": "1.13.7"
}
Expand Down
58 changes: 58 additions & 0 deletions src/actions/read/balance.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
/**
* 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 { ANTState, AntContractReadResult } from 'src/types';

import {
INVALID_INPUT_MESSAGE,
baselineAntState,
} from '../../../tests/utils/constants';
import { balance } from './balance';

describe('balance', () => {
let state: ANTState = { ...baselineAntState };

beforeEach(() => {
state = { ...baselineAntState };
});

it.each(['test', '?', false, Infinity, 0, -1, parseInt(''.padEnd(43, '1'))])(
'should throw error on invalid address type',
async (user: any) => {
const _state = { ...state, balances: { [user]: 1 } };
const result = await balance(_state, {
caller: 'test',
input: {
function: 'balance',
target: user,
},
}).catch((e) => e);
expect(result.message).toEqual(INVALID_INPUT_MESSAGE);
},
);

it('should return 0 for non-existent user', async () => {
const { result } = (await balance(state, {
caller: 'test',
input: {
function: 'balance',
target: ''.padEnd(43, '1'),
},
})) as AntContractReadResult;
const { balance: _balance } = result as any;
expect(_balance).toEqual(0);
});
});
4 changes: 0 additions & 4 deletions src/actions/read/balance.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,10 +33,6 @@ export const balance = async (
throw new ContractError(INVALID_INPUT_MESSAGE);
}

if (typeof target !== 'string') {
throw new ContractError('Must specify target to get balance for');
}

return {
result: { target, ticker, balance: target === owner ? 1 : 0 },
};
Expand Down
77 changes: 60 additions & 17 deletions src/actions/write/evolve.test.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,27 @@
import { INVALID_INPUT_MESSAGE } from '../../constants';
import { ANTState } from '../../types';
/**
* 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 { baselineAntState } from '../../../tests/utils/constants';
import {
INVALID_INPUT_MESSAGE,
NON_CONTRACT_OWNER_MESSAGE,
} from '../../constants';
import { AntContractWriteResult } from '../../types';
import { evolve } from './evolve';

const baselineAntState: ANTState = {
owner: 'test',
evolve: 'test',
controllers: ['test'],
balances: {
test: 1,
},
name: 'test',
records: {},
ticker: 'ANT-TEST',
};

describe('evolve', () => {
it.each([
{
Expand All @@ -40,8 +48,43 @@ describe('evolve', () => {
},
}).catch((e) => e);
expect(error).toBeInstanceOf(Error);
expect(error.message).toEqual(
expect.stringContaining(INVALID_INPUT_MESSAGE),
);
expect(error.message).toEqual(INVALID_INPUT_MESSAGE);
});

it.each(['hacker'])(
'should throw an error on bad caller',
async (badCaller: string) => {
const error: any = await evolve(baselineAntState, {
caller: badCaller,
input: {
function: 'evolve',
value: '1111111111111111111111111111111111111111111',
},
}).catch((e) => e);
expect(error).toBeInstanceOf(Error);
expect(error.message).toEqual(NON_CONTRACT_OWNER_MESSAGE);
},
);

it.each([
{
value: '1111111111111111111111111111111111111111111',
},
])(
'should evolve the contract with valid input',
async (goodInput: { value: string }) => {
const result = (await evolve(baselineAntState, {
caller: 'test',
input: {
function: 'evolve',
...goodInput,
},
})) as AntContractWriteResult;

expect(result.state).toEqual({
...baselineAntState,
evolve: goodInput.value,
});
},
);
});
101 changes: 101 additions & 0 deletions src/actions/write/removeController.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
/**
* 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 { AntContractWriteResult } from 'src/types';

import {
INVALID_INPUT_MESSAGE,
NON_CONTRACT_OWNER_MESSAGE,
baselineAntState,
} from '../../../tests/utils/constants';
import { removeController } from './removeController';

describe('removeController', () => {
let state = { ...baselineAntState };

beforeEach(() => {
state = { ...baselineAntState };
});

it.each([
undefined,
false,
1,
0,
Infinity,
-1,
'?'.padEnd(42, '1'),
''.padEnd(44, '1'),
])('should throw on bad target', async (target: any) => {
const _state = { ...state, controllers: [target] };
const result = await removeController(_state, {
caller: 'test',
input: {
function: 'removeController',
target,
},
}).catch((e) => e);
expect(result.message).toEqual(INVALID_INPUT_MESSAGE);
});

it.each([''.padEnd(43, '1'), ''.padEnd(43, 'a')])(
'should not remove controller as non-owner',
async (target: string) => {
const _state = { ...state, controllers: [target] };
const result = await removeController(_state, {
caller: target,
input: {
function: 'removeController',
target,
},
}).catch((e) => e);
expect(result.message).toEqual(NON_CONTRACT_OWNER_MESSAGE);
},
);

it.each([''.padEnd(43, '1'), ''.padEnd(43, 'a')])(
'should throw if target not a controller',
async (target: string) => {
const _state = { ...state, controllers: [] };
const result = await removeController(_state, {
caller: 'test',
input: {
function: 'removeController',
target,
},
}).catch((e) => e);

expect(result.message).toContain('is not a controller');
expect(result.message).toContain(target);
},
);

it.each([''.padEnd(43, '1'), ''.padEnd(43, 'a')])(
'should remove controller',
async (target: string) => {
const _state = { ...state, controllers: [target] };
const result = (await removeController(_state, {
caller: 'test',
input: {
function: 'removeController',
target,
},
})) as AntContractWriteResult;

expect(result.state).not.toContain(target);
},
);
});
7 changes: 5 additions & 2 deletions src/actions/write/removeController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,10 @@
* 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 { INVALID_INPUT_MESSAGE } from '../../constants';
import {
INVALID_INPUT_MESSAGE,
NON_CONTRACT_OWNER_MESSAGE,
} from '../../constants';
import { ANTState, AntAction, ContractResult } from '../../types';
// composed by ajv at build
import { validateRemoveController } from '../../validations';
Expand All @@ -34,7 +37,7 @@ export const removeController = async (
const owner = state.owner;

if (caller !== owner) {
throw new ContractError(`Caller is not the token owner!`);
throw new ContractError(NON_CONTRACT_OWNER_MESSAGE);
}

if (!state.controllers.includes(target)) {
Expand Down
Loading

0 comments on commit 8637271

Please sign in to comment.