cloak-cli 0.2.0__tar.gz → 0.2.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- cloak_cli-0.2.1/.pre-commit-hooks.yaml +35 -0
- {cloak_cli-0.2.0 → cloak_cli-0.2.1}/CHANGELOG.md +7 -0
- {cloak_cli-0.2.0 → cloak_cli-0.2.1}/PKG-INFO +28 -1
- {cloak_cli-0.2.0 → cloak_cli-0.2.1}/README.md +27 -0
- cloak_cli-0.2.1/examples/README.md +61 -0
- cloak_cli-0.2.1/examples/js-api-client/.cloakpolicy +25 -0
- cloak_cli-0.2.1/examples/js-api-client/client.js +34 -0
- cloak_cli-0.2.1/examples/python-pricing-engine/.cloakpolicy +24 -0
- cloak_cli-0.2.1/examples/python-pricing-engine/pricing.py +43 -0
- cloak_cli-0.2.1/examples/python-pricing-engine/test_pricing.py +19 -0
- {cloak_cli-0.2.0 → cloak_cli-0.2.1}/pyproject.toml +1 -1
- {cloak_cli-0.2.0 → cloak_cli-0.2.1}/src/cloak/__init__.py +1 -1
- {cloak_cli-0.2.0 → cloak_cli-0.2.1}/.cloakpolicy.example +0 -0
- {cloak_cli-0.2.0 → cloak_cli-0.2.1}/.github/workflows/ci.yml +0 -0
- {cloak_cli-0.2.0 → cloak_cli-0.2.1}/.github/workflows/release.yml +0 -0
- {cloak_cli-0.2.0 → cloak_cli-0.2.1}/.gitignore +0 -0
- {cloak_cli-0.2.0 → cloak_cli-0.2.1}/CONTRIBUTING.md +0 -0
- {cloak_cli-0.2.0 → cloak_cli-0.2.1}/LICENSE +0 -0
- {cloak_cli-0.2.0 → cloak_cli-0.2.1}/docs/AGENT_INTEGRATION.md +0 -0
- {cloak_cli-0.2.0 → cloak_cli-0.2.1}/docs/BUILD_PLAN.md +0 -0
- {cloak_cli-0.2.0 → cloak_cli-0.2.1}/docs/research/COMPETITOR_RESEARCH.md +0 -0
- {cloak_cli-0.2.0 → cloak_cli-0.2.1}/docs/research/PROMPTS.md +0 -0
- {cloak_cli-0.2.0 → cloak_cli-0.2.1}/docs/research/quotecraft.py +0 -0
- {cloak_cli-0.2.0 → cloak_cli-0.2.1}/docs/research/quotecraft.redacted.py +0 -0
- {cloak_cli-0.2.0 → cloak_cli-0.2.1}/docs/research/result_prompt1.md +0 -0
- {cloak_cli-0.2.0 → cloak_cli-0.2.1}/docs/research/result_prompt2.md +0 -0
- {cloak_cli-0.2.0 → cloak_cli-0.2.1}/src/cloak/__main__.py +0 -0
- {cloak_cli-0.2.0 → cloak_cli-0.2.1}/src/cloak/cli.py +0 -0
- {cloak_cli-0.2.0 → cloak_cli-0.2.1}/src/cloak/context/__init__.py +0 -0
- {cloak_cli-0.2.0 → cloak_cli-0.2.1}/src/cloak/context/generator.py +0 -0
- {cloak_cli-0.2.0 → cloak_cli-0.2.1}/src/cloak/context/js_redactor.py +0 -0
- {cloak_cli-0.2.0 → cloak_cli-0.2.1}/src/cloak/filesystem.py +0 -0
- {cloak_cli-0.2.0 → cloak_cli-0.2.1}/src/cloak/obfuscate/__init__.py +0 -0
- {cloak_cli-0.2.0 → cloak_cli-0.2.1}/src/cloak/obfuscate/js_transformer.py +0 -0
- {cloak_cli-0.2.0 → cloak_cli-0.2.1}/src/cloak/obfuscate/manifest.py +0 -0
- {cloak_cli-0.2.0 → cloak_cli-0.2.1}/src/cloak/obfuscate/runner.py +0 -0
- {cloak_cli-0.2.0 → cloak_cli-0.2.1}/src/cloak/obfuscate/transformer.py +0 -0
- {cloak_cli-0.2.0 → cloak_cli-0.2.1}/src/cloak/policy.py +0 -0
- {cloak_cli-0.2.0 → cloak_cli-0.2.1}/src/cloak/policy_init.py +0 -0
- {cloak_cli-0.2.0 → cloak_cli-0.2.1}/src/cloak/scan/__init__.py +0 -0
- {cloak_cli-0.2.0 → cloak_cli-0.2.1}/src/cloak/scan/scanner.py +0 -0
- {cloak_cli-0.2.0 → cloak_cli-0.2.1}/tests/__init__.py +0 -0
- {cloak_cli-0.2.0 → cloak_cli-0.2.1}/tests/test_cli.py +0 -0
- {cloak_cli-0.2.0 → cloak_cli-0.2.1}/tests/test_context.py +0 -0
- {cloak_cli-0.2.0 → cloak_cli-0.2.1}/tests/test_context_js.py +0 -0
- {cloak_cli-0.2.0 → cloak_cli-0.2.1}/tests/test_obfuscate.py +0 -0
- {cloak_cli-0.2.0 → cloak_cli-0.2.1}/tests/test_obfuscate_js.py +0 -0
- {cloak_cli-0.2.0 → cloak_cli-0.2.1}/tests/test_policy_init.py +0 -0
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# pre-commit hook definitions for CLOAK.
|
|
2
|
+
#
|
|
3
|
+
# Add to your .pre-commit-config.yaml:
|
|
4
|
+
#
|
|
5
|
+
# repos:
|
|
6
|
+
# - repo: https://github.com/newtophilly/cloak
|
|
7
|
+
# rev: v0.2.0
|
|
8
|
+
# hooks:
|
|
9
|
+
# - id: cloak-scan
|
|
10
|
+
#
|
|
11
|
+
# See https://pre-commit.com for general pre-commit setup.
|
|
12
|
+
|
|
13
|
+
- id: cloak-scan
|
|
14
|
+
name: cloak scan
|
|
15
|
+
description: >-
|
|
16
|
+
Find secrets and proprietary markers in code before commit. Wraps detect-secrets and
|
|
17
|
+
layers in policy.secret_rules from .cloakpolicy. Exits 1 on findings (blocks commit).
|
|
18
|
+
entry: cloak scan
|
|
19
|
+
language: python
|
|
20
|
+
pass_filenames: false
|
|
21
|
+
args: ["."]
|
|
22
|
+
always_run: true
|
|
23
|
+
stages: [pre-commit]
|
|
24
|
+
|
|
25
|
+
- id: cloak-context-preview
|
|
26
|
+
name: cloak context (preview only)
|
|
27
|
+
description: >-
|
|
28
|
+
Render a redacted markdown view of the repo. Useful as a manual check that the redaction
|
|
29
|
+
output looks right. Does not block commits — `stages: [manual]` means run only when you
|
|
30
|
+
invoke it explicitly: `pre-commit run cloak-context-preview --hook-stage manual`.
|
|
31
|
+
entry: cloak context
|
|
32
|
+
language: python
|
|
33
|
+
pass_filenames: false
|
|
34
|
+
args: [".", "--json"]
|
|
35
|
+
stages: [manual]
|
|
@@ -6,6 +6,13 @@ The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and
|
|
|
6
6
|
|
|
7
7
|
## [Unreleased]
|
|
8
8
|
|
|
9
|
+
## [0.2.1] - 2026-05-08
|
|
10
|
+
|
|
11
|
+
### Added
|
|
12
|
+
- `.pre-commit-hooks.yaml` at repo root. Users can add `cloak-scan` to their `.pre-commit-config.yaml` and `cloak scan` runs as a pre-commit gate (exits 1 on findings, blocks the commit). A second `cloak-context-preview` hook is provided at `stages: [manual]` for opt-in invocation.
|
|
13
|
+
- `examples/` directory with two end-to-end demos: `python-pricing-engine/` (with pytest coverage so `--verify "pytest"` works) and `js-api-client/`. Each has its own `.cloakpolicy`. New users can `cd examples/python-pricing-engine && cloak scan .` and see CLOAK in action in 30 seconds. See `examples/README.md` for the walkthrough.
|
|
14
|
+
- README "Try it on the included examples" + "Use as a pre-commit hook" sections.
|
|
15
|
+
|
|
9
16
|
## [0.2.0] - 2026-05-08
|
|
10
17
|
|
|
11
18
|
Feature matrix complete: all three headline commands now work for both Python and JS/TS.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: cloak-cli
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.1
|
|
4
4
|
Summary: Local CLI for safer LLM workflows: redact code before pasting, generate verified obfuscated copies, enforce policy from your repo.
|
|
5
5
|
Project-URL: Homepage, https://github.com/newtophilly/cloak
|
|
6
6
|
Project-URL: Repository, https://github.com/newtophilly/cloak
|
|
@@ -324,6 +324,33 @@ $ cloak obfuscate src/payments --out /tmp/payments.cloaked --verify "pytest test
|
|
|
324
324
|
$ cloak scan . --json # exits 1 if any secrets, JSON for parsing.
|
|
325
325
|
```
|
|
326
326
|
|
|
327
|
+
### Try it on the included examples
|
|
328
|
+
|
|
329
|
+
The repo ships [`examples/`](examples/) with one Python and one JS project, each with its own `.cloakpolicy`. Clone, install, and run end-to-end against either in 30 seconds:
|
|
330
|
+
|
|
331
|
+
```bash
|
|
332
|
+
cd examples/python-pricing-engine
|
|
333
|
+
cloak scan .
|
|
334
|
+
cloak context . --copy
|
|
335
|
+
cloak obfuscate . --out /tmp/pricing.cloaked --verify "pytest"
|
|
336
|
+
```
|
|
337
|
+
|
|
338
|
+
See [examples/README.md](examples/README.md) for the full walkthrough.
|
|
339
|
+
|
|
340
|
+
### Use as a pre-commit hook
|
|
341
|
+
|
|
342
|
+
Drop `cloak scan` into `.pre-commit-config.yaml` to block commits that introduce secrets:
|
|
343
|
+
|
|
344
|
+
```yaml
|
|
345
|
+
repos:
|
|
346
|
+
- repo: https://github.com/newtophilly/cloak
|
|
347
|
+
rev: v0.2.0
|
|
348
|
+
hooks:
|
|
349
|
+
- id: cloak-scan
|
|
350
|
+
```
|
|
351
|
+
|
|
352
|
+
Then `pre-commit install` and you're done. See [pre-commit.com](https://pre-commit.com) for general setup.
|
|
353
|
+
|
|
327
354
|
## How `.cloakpolicy` works
|
|
328
355
|
|
|
329
356
|
The policy lives in a `.cloakpolicy` YAML file at the repo root. It's checked into git, versioned with your code, and reviewed via the same PR process as everything else. Authority = whoever has merge access.
|
|
@@ -83,6 +83,33 @@ $ cloak obfuscate src/payments --out /tmp/payments.cloaked --verify "pytest test
|
|
|
83
83
|
$ cloak scan . --json # exits 1 if any secrets, JSON for parsing.
|
|
84
84
|
```
|
|
85
85
|
|
|
86
|
+
### Try it on the included examples
|
|
87
|
+
|
|
88
|
+
The repo ships [`examples/`](examples/) with one Python and one JS project, each with its own `.cloakpolicy`. Clone, install, and run end-to-end against either in 30 seconds:
|
|
89
|
+
|
|
90
|
+
```bash
|
|
91
|
+
cd examples/python-pricing-engine
|
|
92
|
+
cloak scan .
|
|
93
|
+
cloak context . --copy
|
|
94
|
+
cloak obfuscate . --out /tmp/pricing.cloaked --verify "pytest"
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
See [examples/README.md](examples/README.md) for the full walkthrough.
|
|
98
|
+
|
|
99
|
+
### Use as a pre-commit hook
|
|
100
|
+
|
|
101
|
+
Drop `cloak scan` into `.pre-commit-config.yaml` to block commits that introduce secrets:
|
|
102
|
+
|
|
103
|
+
```yaml
|
|
104
|
+
repos:
|
|
105
|
+
- repo: https://github.com/newtophilly/cloak
|
|
106
|
+
rev: v0.2.0
|
|
107
|
+
hooks:
|
|
108
|
+
- id: cloak-scan
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
Then `pre-commit install` and you're done. See [pre-commit.com](https://pre-commit.com) for general setup.
|
|
112
|
+
|
|
86
113
|
## How `.cloakpolicy` works
|
|
87
114
|
|
|
88
115
|
The policy lives in a `.cloakpolicy` YAML file at the repo root. It's checked into git, versioned with your code, and reviewed via the same PR process as everything else. Authority = whoever has merge access.
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
# CLOAK examples
|
|
2
|
+
|
|
3
|
+
Two tiny projects to demonstrate `cloak scan`, `cloak context`, and `cloak obfuscate` end to end.
|
|
4
|
+
|
|
5
|
+
Both have their own `.cloakpolicy` so you can run CLOAK against them right away.
|
|
6
|
+
|
|
7
|
+
## `python-pricing-engine/`
|
|
8
|
+
|
|
9
|
+
A simplified pricing engine: a public `calculate_total` plus private `_apply_tier` and `_apply_region` helpers, with `_TIER_DISCOUNTS` and `_REGIONAL_MARKUPS` proprietary tables. Includes pytest coverage so `--verify` has something real to check.
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
cd examples/python-pricing-engine
|
|
13
|
+
|
|
14
|
+
# 1. Scan for secrets / proprietary markers
|
|
15
|
+
cloak scan .
|
|
16
|
+
|
|
17
|
+
# 2. Generate redacted markdown safe to paste into ChatGPT/Claude
|
|
18
|
+
cloak context . --copy
|
|
19
|
+
# Bodies replaced with `...`. Tables (_TIER_DISCOUNTS, _REGIONAL_MARKUPS) replaced
|
|
20
|
+
# with `...`. Public API + signatures preserved.
|
|
21
|
+
|
|
22
|
+
# 3. Obfuscate, gated on the pytest suite passing
|
|
23
|
+
cloak obfuscate . --out /tmp/pricing.cloaked --verify "pytest"
|
|
24
|
+
# Private helpers renamed (_apply_tier → _a000, _apply_region → _a001).
|
|
25
|
+
# Public `calculate_total` preserved (it's in .cloakpolicy public_api).
|
|
26
|
+
# Tests pass against the obfuscated copy.
|
|
27
|
+
ls /tmp/pricing.cloaked/cloak-manifest.json # audit trail with hashes + rename map
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
## `js-api-client/`
|
|
31
|
+
|
|
32
|
+
A small API client with a public `fetchJson` plus private `_buildHeaders` and `_normalizePath` helpers, and `_BASE_HEADERS` / `_TIMEOUT_MS` proprietary constants.
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
cd examples/js-api-client
|
|
36
|
+
|
|
37
|
+
cloak scan .
|
|
38
|
+
cloak context . --copy
|
|
39
|
+
cloak obfuscate . --out /tmp/client.cloaked
|
|
40
|
+
# Note: no --verify here because the example doesn't ship a Node test runner setup.
|
|
41
|
+
# In your real project, you'd pass --verify "npm test" or similar.
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
## What you should observe
|
|
45
|
+
|
|
46
|
+
- `cloak scan` exits 0 (no secrets in these examples) and prints a green "Clean" panel.
|
|
47
|
+
- `cloak context` produces markdown where signatures and class shapes survive, but bodies and proprietary tables are replaced with `...` (Python) or `/* [REDACTED BY CLOAK] */` (JS/TS).
|
|
48
|
+
- `cloak obfuscate` produces a transformed copy in the output dir with `_a000`/`_a001`/... identifiers replacing the original `_names`. Public-API names listed in `.cloakpolicy` are preserved.
|
|
49
|
+
- The `cloak-manifest.json` in the output dir records: cloak version, source/output sha256s, the rename map, the policy snapshot, and (if `--verify` was passed) the verify command + result.
|
|
50
|
+
|
|
51
|
+
## Want to try `--verify` failing?
|
|
52
|
+
|
|
53
|
+
Edit `pricing.py` to break the math (e.g., return `subtotal` instead of the discounted total) and rerun:
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
cloak obfuscate . --out /tmp/pricing.cloaked --verify "pytest"
|
|
57
|
+
# Exit code 1, panel shows the failing pytest output.
|
|
58
|
+
# Output is still written for inspection but the operation is reported as failed.
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
This is the differentiator: `cloak obfuscate` only succeeds if your tests pass against the transformed copy.
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# Example policy for the JS API client.
|
|
2
|
+
version: 1
|
|
3
|
+
|
|
4
|
+
sensitive_paths:
|
|
5
|
+
- "*.js"
|
|
6
|
+
- "*.ts"
|
|
7
|
+
|
|
8
|
+
# `fetchJson` is the public API — never let obfuscate rename it.
|
|
9
|
+
public_api:
|
|
10
|
+
- "fetchJson"
|
|
11
|
+
|
|
12
|
+
secret_rules: []
|
|
13
|
+
allow_strings: []
|
|
14
|
+
|
|
15
|
+
context_defaults:
|
|
16
|
+
keep_docstrings: true
|
|
17
|
+
redact_function_bodies: true
|
|
18
|
+
alias_enums: false
|
|
19
|
+
|
|
20
|
+
obfuscate_defaults:
|
|
21
|
+
rename_private: true
|
|
22
|
+
rename_public_api: false
|
|
23
|
+
encode_strings: false
|
|
24
|
+
strip_docstrings: false
|
|
25
|
+
profile: standard
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
// Tiny example API client — public function plus underscore-private helpers.
|
|
2
|
+
//
|
|
3
|
+
// Demonstrates how `cloak context` and `cloak obfuscate` treat JS files.
|
|
4
|
+
|
|
5
|
+
const _BASE_HEADERS = {
|
|
6
|
+
"User-Agent": "example-client/1.0",
|
|
7
|
+
"Accept": "application/json",
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
const _TIMEOUT_MS = 5000;
|
|
11
|
+
|
|
12
|
+
function _buildHeaders(token) {
|
|
13
|
+
return {
|
|
14
|
+
..._BASE_HEADERS,
|
|
15
|
+
Authorization: `Bearer ${token}`,
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function _normalizePath(path) {
|
|
20
|
+
if (!path.startsWith("/")) return `/${path}`;
|
|
21
|
+
return path;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export async function fetchJson(baseUrl, path, token) {
|
|
25
|
+
const url = baseUrl + _normalizePath(path);
|
|
26
|
+
const response = await fetch(url, {
|
|
27
|
+
headers: _buildHeaders(token),
|
|
28
|
+
signal: AbortSignal.timeout(_TIMEOUT_MS),
|
|
29
|
+
});
|
|
30
|
+
if (!response.ok) {
|
|
31
|
+
throw new Error(`HTTP ${response.status}`);
|
|
32
|
+
}
|
|
33
|
+
return response.json();
|
|
34
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# Example policy for the Python pricing engine.
|
|
2
|
+
version: 1
|
|
3
|
+
|
|
4
|
+
sensitive_paths:
|
|
5
|
+
- "*.py"
|
|
6
|
+
|
|
7
|
+
# `calculate_total` is the public entry point — never let obfuscate rename it.
|
|
8
|
+
public_api:
|
|
9
|
+
- "calculate_total"
|
|
10
|
+
|
|
11
|
+
secret_rules: []
|
|
12
|
+
allow_strings: []
|
|
13
|
+
|
|
14
|
+
context_defaults:
|
|
15
|
+
keep_docstrings: true
|
|
16
|
+
redact_function_bodies: true
|
|
17
|
+
alias_enums: false
|
|
18
|
+
|
|
19
|
+
obfuscate_defaults:
|
|
20
|
+
rename_private: true
|
|
21
|
+
rename_public_api: false
|
|
22
|
+
encode_strings: false
|
|
23
|
+
strip_docstrings: false
|
|
24
|
+
profile: standard
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
"""Tiny example pricing engine — public API + private helpers + 'proprietary tables'.
|
|
2
|
+
|
|
3
|
+
This file exists to demonstrate how `cloak context` and `cloak obfuscate` treat each kind
|
|
4
|
+
of definition. It is intentionally small and self-contained.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from dataclasses import dataclass
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
_TIER_DISCOUNTS = {
|
|
11
|
+
"basic": 0.00,
|
|
12
|
+
"pro": 0.10,
|
|
13
|
+
"enterprise": 0.20,
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
_REGIONAL_MARKUPS = {
|
|
17
|
+
"domestic": 1.00,
|
|
18
|
+
"international": 1.15,
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
@dataclass
|
|
23
|
+
class Customer:
|
|
24
|
+
customer_id: str
|
|
25
|
+
tier: str
|
|
26
|
+
region: str
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def calculate_total(customer: Customer, subtotal: float) -> float:
|
|
30
|
+
"""Public API: produce a final price for a customer + subtotal."""
|
|
31
|
+
discount = _apply_tier(customer.tier)
|
|
32
|
+
markup = _apply_region(customer.region)
|
|
33
|
+
return subtotal * (1 - discount) * markup
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def _apply_tier(tier: str) -> float:
|
|
37
|
+
"""Return the discount fraction for a tier."""
|
|
38
|
+
return _TIER_DISCOUNTS.get(tier, 0.0)
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def _apply_region(region: str) -> float:
|
|
42
|
+
"""Return the price multiplier for a region."""
|
|
43
|
+
return _REGIONAL_MARKUPS.get(region, 1.0)
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
"""Tests for the example pricing engine — used by `cloak obfuscate --verify`."""
|
|
2
|
+
|
|
3
|
+
from pricing import Customer, calculate_total
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def test_basic_domestic() -> None:
|
|
7
|
+
c = Customer(customer_id="C1", tier="basic", region="domestic")
|
|
8
|
+
assert calculate_total(c, 100.0) == 100.0
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def test_pro_domestic_gets_10_percent() -> None:
|
|
12
|
+
c = Customer(customer_id="C2", tier="pro", region="domestic")
|
|
13
|
+
assert calculate_total(c, 100.0) == 90.0
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def test_enterprise_international() -> None:
|
|
17
|
+
c = Customer(customer_id="C3", tier="enterprise", region="international")
|
|
18
|
+
# 100 * 0.80 * 1.15 = 92.0
|
|
19
|
+
assert calculate_total(c, 100.0) == 92.0
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|