Skip to content

Commit

Permalink
feat: typescript test support and example simulate test
Browse files Browse the repository at this point in the history
* feat: adding support for typescript tests
* feat: support new client generator simulate
  • Loading branch information
neilcampbell authored Dec 18, 2023
1 parent ad9331e commit 6a21537
Show file tree
Hide file tree
Showing 239 changed files with 1,683 additions and 833 deletions.
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

0 comments on commit 6a21537

Please sign in to comment.