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 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 .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 includes/contract_name_kebab.jinja
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{{- contract_name.split('_')|join('-') -}}
1 change: 1 addition & 0 deletions includes/contract_name_pascal.jinja
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{{- contract_name.split('_')|map('capitalize')|join -}}
2 changes: 1 addition & 1 deletion template_content/.algokit.toml.jinja
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
[algokit]
min_version = "v1.7.3"
min_version = "v1.8.0"

[deploy]
{%- if deployment_language == 'python' %}
Expand Down
4 changes: 4 additions & 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 Expand Up @@ -172,3 +173,6 @@ cython_debug/

# NPM
node_modules

# AlgoKit
debug_traces/
25 changes: 15 additions & 10 deletions template_content/README.md.jinja
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,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 @@ -131,12 +134,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 @@ -149,14 +152,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 = []
2 changes: 1 addition & 1 deletion template_content/smart_contracts/helpers/build.py.jinja
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ def build(output_dir: Path, app: beaker.Application) -> Path:
if result.returncode:
if "No such command" in result.stdout:
raise Exception(
"Could not generate typed client, requires AlgoKit 1.1 or "
"Could not generate typed client, requires AlgoKit 1.8.0 or "
"later. Please update AlgoKit"
)
else:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,10 @@ def deploy(
deployer: algokit_utils.Account,
) -> None:
from smart_contracts.artifacts.{{ contract_name }}.client import (
{{ contract_name.split('_')|map('capitalize')|join }}Client,
{% include pathjoin('includes', 'contract_name_pascal.jinja') %}Client,
)

app_client = {{ contract_name.split('_')|map('capitalize')|join }}Client(
app_client = {% include pathjoin('includes', 'contract_name_pascal.jinja') %}Client(
algod_client,
creator=deployer,
indexer_client=indexer_client,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import * as algokit from '@algorandfoundation/algokit-utils'
import { {{ contract_name.split('_')|map('capitalize')|join }}Client } from '../artifacts/{{ contract_name }}/client'
import { {% include pathjoin('includes', 'contract_name_pascal.jinja') %}Client } from '../artifacts/{{ contract_name }}/client'

// Below is a showcase of various deployment options you can use in TypeScript Client
export async function deploy() {
console.log('=== Deploying {{ contract_name.split('_')|map('capitalize')|join }} ===')
console.log('=== Deploying {% include pathjoin('includes', 'contract_name_pascal.jinja') %} ===')

const algod = algokit.getAlgoClient()
const indexer = algokit.getAlgoIndexerClient()
Expand All @@ -16,7 +16,7 @@ export async function deploy() {
},
algod,
)
const appClient = new {{ contract_name.split('_')|map('capitalize')|join }}Client(
const appClient = new {% include pathjoin('includes', 'contract_name_pascal.jinja') %}Client(
{
resolveBy: 'creatorAndName',
findExistingUsing: indexer,
Expand Down
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 @@ -23,9 +23,9 @@
"line": 32
},
{
"file": "tests/hello_world_test.py",
"description": "If you opted to include unit tests, the default tests provided demonstrate an example of mocking, setting up fixtures, and testing smart contract calls on an AlgoKit application client.",
"line": 21
"file": "tests/{% if deployment_language == 'typescript' %}{% include pathjoin('includes', 'contract_name_kebab.jinja') %}.spec.ts{% else %}{{ contract_name }}_test.py{% endif %}",
"description": "If you opted to include unit tests, the default tests provided demonstrate an example of mocking, setting up fixtures, and testing smart contract calls on an AlgoKit typed client.",
"line": {% if deployment_language == 'typescript' %}39{% else %}36{% endif %}
},
{
"file": ".env.localnet.template",
Expand Down
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,50 @@
import algokit_utils
import pytest
from algokit_utils import (
ApplicationClient,
ApplicationSpecification,
get_localnet_default_account,
)
from algokit_utils import get_localnet_default_account
from algokit_utils.config import config
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 {% include pathjoin('includes', 'contract_name_pascal.jinja') %}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
) -> {% include pathjoin('includes', 'contract_name_pascal.jinja') %}Client:
config.configure(
debug=True,
# trace_all=True,
)

client = {% include pathjoin('includes', 'contract_name_pascal.jinja') %}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: {% include pathjoin('includes', 'contract_name_pascal.jinja') %}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: {% include pathjoin('includes', 'contract_name_pascal.jinja') %}Client, algod_client: AlgodClient
) -> None:
result = (
{{ contract_name }}_client.compose().hello(name="World").hello(name="Jane").simulate()
)

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"] < 100
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
Loading