Skip to content
This repository has been archived by the owner on Nov 27, 2024. It is now read-only.

Contract simulate tests #51

Merged
merged 17 commits into from
Dec 18, 2023
Merged
Show file tree
Hide file tree
Changes from 8 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 .github/workflows/cd.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ jobs:
skip-commit: "true"
tag-prefix: ""
skip-on-empty: "false"

- name: Create Release
uses: softprops/action-gh-release@v1
if: ${{ steps.tag.outputs.skipped == 'false' }}
Expand Down
28 changes: 23 additions & 5 deletions copier.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,29 @@ deployment_language:
TypeScript: "typescript"
default: "python"

use_python_pytest:
type: bool
when: "{{ deployment_language == 'python' }}"
help: Do you want to include unit tests (via pytest)?
# deployment_language is empty when using the default_language
default: |-
{% if deployment_language|length == 0 or deployment_language == 'python' -%}
yes
{%- else -%}
no
{%- endif %}

use_typescript_jest:
type: bool
when: "{{ deployment_language == 'typescript' }}"
help: Do you want to include unit tests (via jest)?
default: |-
{% if deployment_language == 'typescript' -%}
yes
{%- else -%}
no
{%- endif %}

python_linter:
type: str
help: Do you want to use a Python linter?
Expand Down Expand Up @@ -86,11 +109,6 @@ use_python_mypy:
no
{%- endif %}

use_python_pytest:
type: bool
help: Do you want to include unit tests (via pytest)?
default: yes

use_python_pip_audit:
type: bool
when: "{{ preset_name == 'production' }}"
Expand Down
1 change: 1 addition & 0 deletions template_content/.gitignore.jinja
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ coverage.xml
.hypothesis/
.pytest_cache/
cover/
coverage/

# Translations
*.mo
Expand Down
25 changes: 15 additions & 10 deletions template_content/README.md.jinja
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,9 @@ For pull requests and pushes to `main` branch against this repository the follow
{%- endif %}
{%- if use_python_pytest %}
- Python tests are executed using [pytest](https://docs.pytest.org/)
{%- endif %}
{%- if use_typescript_jest %}
- Typescript tests are executed using [Jest](https://jestjs.io/)
{%- endif %}
- Smart contract artifacts are built
- Smart contract artifacts are checked for [output stability](https://github.com/algorandfoundation/algokit-cli/blob/main/docs/articles/output_stability.md)
Expand All @@ -116,12 +119,12 @@ This project makes use of Python to build Algorand smart contracts. The followin
- [PyTEAL](https://github.com/algorand/pyteal) - Python language binding for Algorand smart contracts; [docs](https://pyteal.readthedocs.io/en/stable/)
- [AlgoKit Utils]({% if deployment_language == "typescript" %}https://github.com/algorandfoundation/algokit-utils-ts{% else %}https://github.com/algorandfoundation/algokit-utils-py{% endif %}) - A set of core Algorand utilities that make it easier to build solutions on Algorand.
- [Poetry](https://python-poetry.org/): Python packaging and dependency management.
{%- if use_python_black -%}
{%- if use_python_black %}
- [Black](https://github.com/psf/black): A Python code formatter.
{%- endif %}
{%- if python_linter == "ruff" -%}
{%- if python_linter == "ruff" %}
- [Ruff](https://github.com/charliermarsh/ruff): An extremely fast Python linter.
{% elif python_linter == "flake8" -%}
{%- elif python_linter == "flake8" %}
- [Flake8](https://flake8.pycqa.org/en/latest/): A Python linter for style guide enforcement.
{%- endif %}
{%- if use_python_mypy %}
Expand All @@ -134,14 +137,16 @@ This project makes use of Python to build Algorand smart contracts. The followin
- [pip-audit](https://pypi.org/project/pip-audit/): Tool for scanning Python environments for packages with known vulnerabilities.
{%- endif %}
{%- if use_pre_commit %}
- [pre-commit](https://pre-commit.com/): A framework for managing and maintaining multi-language pre-commit hooks, to enable pre-commit you need to run `pre-commit install` in the root of the repository. This will install the pre-commit hooks and run them against modified files when committing. If any of the hooks fail, the commit will be aborted. To run the hooks on all files, use `pre-commit run --all-files`.
- [pre-commit](https://pre-commit.com/): A framework for managing and maintaining multi-language pre-commit hooks, to enable pre-commit you need to run `pre-commit install` in the root of the repository. This will install the pre-commit hooks and run them against modified files when committing. If any of the hooks fail, the commit will be aborted. To run the hooks on all files, use `pre-commit run --all-files`.
{%- endif %}
{%- if deployment_language == "typescript" %}
- [npm](https://www.npmjs.com/): Node.js package manager
- [TypeScript](https://www.typescriptlang.org/): Strongly typed programming language that builds on JavaScript
- [ts-node-dev](https://github.com/wclr/ts-node-dev): TypeScript development execution environment
{% endif -%}

- [npm](https://www.npmjs.com/): Node.js package manager.
- [TypeScript](https://www.typescriptlang.org/): Strongly typed programming language that builds on JavaScript.
- [ts-node-dev](https://github.com/wclr/ts-node-dev): TypeScript development execution environment.
{%- endif %}
{%- if use_typescript_jest %}
- [Jest](https://jestjs.io/): Automated testing.
{%- endif %}
{% if ide_vscode %}
It has also been configured to have a productive dev experience out of the box in [VS Code](https://code.visualstudio.com/), see the [.vscode](./.vscode) folder.
{% endif %}
{%- endif %}
2 changes: 1 addition & 1 deletion template_content/smart_contracts/config.py.jinja
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,6 @@ contracts = [

## Comment the above and uncomment the below and define contracts manually if you want to build and specify them
## manually otherwise the above code will always include all contracts under contract.py file for any subdirectory
## in the smart_contracts directory. Optionally it will also grab the deploy function from deploy_config.py if it exists.
## in the smart_contracts directory. Optionally it will grab the deploy function from deploy_config.py if it exists.

# contracts = []
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@
"scripts": {
"deploy": "ts-node-dev --transpile-only --watch .env -r dotenv/config smart_contracts/index.ts",
"deploy:ci": "ts-node --transpile-only -r dotenv/config smart_contracts/index.ts",
{%- if use_typescript_jest %}
"test": "jest --coverage",
{%- endif %}
"format": "prettier --write ."
},
"engines": {
Expand All @@ -16,8 +19,14 @@
"algosdk": "^2.5.0"
},
"devDependencies": {
{%- if use_typescript_jest %}
"@types/jest": "^29.5.11",
{%- endif %}
"dotenv": "^16.0.3",
"prettier": "^2.8.4",
{%- if use_typescript_jest %}
"ts-jest": "^29.1.1",
{%- endif %}
"ts-node-dev": "^2.0.0",
"typescript": "^4.9.5"
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
{
"compilerOptions": {
"target": "ES2020",
"declaration": true,
"declarationMap": true,
"sourceMap": true,
"strict": true,
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true,
"esModuleInterop": true,
"experimentalDecorators": true,
"emitDecoratorMetadata": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"allowJs": false,
"allowSyntheticDefaultImports": true,
"moduleResolution": "Node",
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true
},
"include": ["src/**/*.ts"],
"exclude": ["node_modules", "dist", "coverage"]
}
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,9 @@ jobs:
# stop the build if there are Python syntax errors or undefined names
poetry run flake8 .
{%- endif %}

- name: Build smart contracts
run: poetry run python -m smart_contracts build
{%- if use_python_mypy %}

- name: Check types with mypy
Expand All @@ -79,9 +82,12 @@ jobs:
set -o pipefail
poetry run pytest --junitxml=pytest-junit.xml
{%- endif %}
{%- if use_typescript_jest %}

- name: Build smart contracts
run: poetry run python -m smart_contracts build
- name: Run tests
shell: bash
run: npm run test
{%- endif %}

- name: Check output stability of the smart contracts
shell: bash
Expand All @@ -93,7 +99,7 @@ jobs:

- name: Run deployer against LocalNet
{%- if deployment_language == 'typescript' %}
run: npm run --prefix smart_contracts deploy:ci
run: npm run deploy:ci
{%- elif deployment_language == 'python' %}
run: poetry run python -m smart_contracts deploy
{%- endif %}
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,11 @@
import pytest
from algokit_utils import (
get_algod_client,
get_indexer_client,
is_localnet,
)
from algosdk.v2client.algod import AlgodClient
from algosdk.v2client.indexer import IndexerClient
from dotenv import load_dotenv


Expand All @@ -23,3 +25,8 @@ def algod_client() -> AlgodClient:
# included here to prevent accidentally running against other networks
assert is_localnet(client)
return client


@pytest.fixture(scope="session")
def indexer_client() -> IndexerClient:
return get_indexer_client()
Original file line number Diff line number Diff line change
@@ -1,36 +1,44 @@
import algokit_utils
import pytest
from algokit_utils import (
ApplicationClient,
ApplicationSpecification,
get_localnet_default_account,
)
from algokit_utils import get_localnet_default_account
from algosdk.v2client.algod import AlgodClient
from algosdk.v2client.indexer import IndexerClient

from smart_contracts.{{ contract_name }} import contract as {{ contract_name }}_contract


@pytest.fixture(scope="session")
def {{ contract_name }}_app_spec(algod_client: AlgodClient) -> ApplicationSpecification:
return {{ contract_name }}_contract.app.build(algod_client)
from smart_contracts.artifacts.{{ contract_name }}.client import {{ contract_name.split('_')|map('capitalize')|join }}Client


@pytest.fixture(scope="session")
def {{ contract_name }}_client(
algod_client: AlgodClient, {{ contract_name }}_app_spec: ApplicationSpecification
) -> ApplicationClient:
client = ApplicationClient(
algod_client: AlgodClient, indexer_client: IndexerClient
) -> {{ contract_name.split('_')|map('capitalize')|join }}Client:
client = {{ contract_name.split('_')|map('capitalize')|join }}Client(
algod_client,
app_spec={{ contract_name }}_app_spec,
signer=get_localnet_default_account(algod_client),
{%- if preset_name == 'production' %}
template_values={"UPDATABLE": 1, "DELETABLE": 1},
{%- endif %}
creator=get_localnet_default_account(algod_client),
indexer_client=indexer_client,
)

client.deploy(
on_schema_break=algokit_utils.OnSchemaBreak.ReplaceApp,
on_update=algokit_utils.OnUpdate.UpdateApp,
allow_delete=True,
allow_update=True,
)
client.create()
return client


def test_says_hello({{ contract_name }}_client: ApplicationClient) -> None:
result = {{ contract_name }}_client.call({{ contract_name }}_contract.hello, name="World")
def test_says_hello({{ contract_name }}_client: {{ contract_name.split('_')|map('capitalize')|join }}Client) -> None:
result = {{ contract_name }}_client.hello(name="World")

assert result.return_value == "Hello, World"


def test_simulate_says_hello_with_correct_budget_consumed(
{{ contract_name }}_client: {{ contract_name.split('_')|map('capitalize')|join }}Client, algod_client: AlgodClient
) -> None:
atc = {{ contract_name }}_client.compose().hello(name="World").hello(name="Jane").atc

result = atc.simulate(algod_client)

assert result.abi_results[0].return_value == "Hello, World"
assert result.abi_results[1].return_value == "Hello, Jane"
assert result.simulate_response["txn-groups"][0]["app-budget-consumed"] == 98
neilcampbell marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
/*
* For a detailed explanation regarding each configuration property and type check, visit:
* https://jestjs.io/docs/configuration
*/
import type { Config } from 'jest'

const config: Config = {
preset: 'ts-jest',
verbose: true,
transform: {
'^.+\\.tsx?$': 'ts-jest',
},
testPathIgnorePatterns: ['node_modules', '.venv', 'coverage'],
testTimeout: 10000,
}
export default config
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import { algorandFixture } from '@algorandfoundation/algokit-utils/testing'
import { {{ contract_name.split('_')|map('capitalize')|join }}Client } from '../smart_contracts/artifacts/{{ contract_name }}/client'
import { Account, Algodv2, Indexer } from 'algosdk'

describe('{{ contract_name.split('_')|join(' ') }} contract', () => {
const localnet = algorandFixture()
beforeEach(localnet.beforeEach)

const deploy = async (account: Account, algod: Algodv2, indexer: Indexer) => {
const client = new {{ contract_name.split('_')|map('capitalize')|join }}Client(
{
resolveBy: 'creatorAndName',
findExistingUsing: indexer,
sender: account,
creatorAddress: account.addr,
},
algod,
)
await client.deploy({
allowDelete: true,
allowUpdate: true,
onSchemaBreak: 'replace',
onUpdate: 'update',
})
return { client }
}

test('says hello', async () => {
const { algod, indexer, testAccount } = localnet.context
const { client } = await deploy(testAccount, algod, indexer)

const result = await client.hello({ name: 'World' })

expect(result.return).toBe('Hello, World')
})

test('simulate says hello with correct budget consumed', async () => {
const { algod, indexer, testAccount } = localnet.context
const { client } = await deploy(testAccount, algod, indexer)
const atc = await client.compose().hello({ name: 'World' }).hello({ name: 'Jane' }).atc()
neilcampbell marked this conversation as resolved.
Show resolved Hide resolved

const result = await atc.simulate(algod)

expect(result.methodResults[0].returnValue).toBe('Hello, World')
expect(result.methodResults[1].returnValue).toBe('Hello, Jane')
expect(result.simulateResponse.txnGroups[0].appBudgetConsumed).toBe(98)
})
})
3 changes: 3 additions & 0 deletions tests/test_templates.py
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,9 @@ def get_questions_from_copier_yaml(
"use_python_pip_audit",
"use_dispenser",
"use_pre_commit",
# this also needs deployment_language set to typescript
# and is already tested via the typescript tests
"use_typescript_jest",
}
ignored_keys.update(DEFAULT_PARAMETERS)

Expand Down
1 change: 1 addition & 0 deletions tests_generated/test_default_parameters/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ coverage.xml
.hypothesis/
.pytest_cache/
cover/
coverage/

# Translations
*.mo
Expand Down
5 changes: 3 additions & 2 deletions tests_generated/test_default_parameters/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,8 +50,9 @@ This project makes use of Python to build Algorand smart contracts. The followin
- [Beaker](https://github.com/algorand-devrel/beaker) - Smart contract development framework for PyTeal; [docs](https://beaker.algo.xyz), [examples](https://github.com/algorand-devrel/beaker/tree/master/examples)
- [PyTEAL](https://github.com/algorand/pyteal) - Python language binding for Algorand smart contracts; [docs](https://pyteal.readthedocs.io/en/stable/)
- [AlgoKit Utils](https://github.com/algorandfoundation/algokit-utils-py) - A set of core Algorand utilities that make it easier to build solutions on Algorand.
- [Poetry](https://python-poetry.org/): Python packaging and dependency management.- [Black](https://github.com/psf/black): A Python code formatter.
- [Poetry](https://python-poetry.org/): Python packaging and dependency management.
- [Black](https://github.com/psf/black): A Python code formatter.
- [pytest](https://docs.pytest.org/): Automated testing.
- [pip-audit](https://pypi.org/project/pip-audit/): Tool for scanning Python environments for packages with known vulnerabilities.
It has also been configured to have a productive dev experience out of the box in [VS Code](https://code.visualstudio.com/), see the [.vscode](./.vscode) folder.

It has also been configured to have a productive dev experience out of the box in [VS Code](https://code.visualstudio.com/), see the [.vscode](./.vscode) folder.
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,6 @@ def has_contract_file(directory: Path) -> bool:

## Comment the above and uncomment the below and define contracts manually if you want to build and specify them
## manually otherwise the above code will always include all contracts under contract.py file for any subdirectory
## in the smart_contracts directory. Optionally it will also grab the deploy function from deploy_config.py if it exists.
## in the smart_contracts directory. Optionally it will grab the deploy function from deploy_config.py if it exists.

# contracts = []
Loading