behave-data 0.1.2__tar.gz → 0.1.3__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.
- {behave_data-0.1.2 → behave_data-0.1.3}/CHANGELOG.md +19 -0
- {behave_data-0.1.2 → behave_data-0.1.3}/PKG-INFO +1 -1
- {behave_data-0.1.2 → behave_data-0.1.3}/behave_data/__init__.py +1 -1
- {behave_data-0.1.2 → behave_data-0.1.3}/behave_data/tags.py +11 -6
- {behave_data-0.1.2 → behave_data-0.1.3}/pyproject.toml +1 -1
- behave_data-0.1.3/tests/integration/environment.py +48 -0
- behave_data-0.1.3/tests/integration/features/data/products.json +5 -0
- behave_data-0.1.3/tests/integration/features/data/users.csv +4 -0
- behave_data-0.1.3/tests/integration/features/dynamic_examples.feature +19 -0
- behave_data-0.1.3/tests/integration/features/fixtures_builders.feature +34 -0
- behave_data-0.1.3/tests/integration/features/secrets.feature +19 -0
- behave_data-0.1.3/tests/integration/features/typed_table_edge.feature +37 -0
- behave_data-0.1.3/tests/integration/steps/e2e_steps.py +247 -0
- behave_data-0.1.3/tests/unit/test_edge_cases.py +613 -0
- behave_data-0.1.2/tests/integration/environment.py +0 -14
- {behave_data-0.1.2 → behave_data-0.1.3}/.github/ISSUE_TEMPLATE/bug_report.yml +0 -0
- {behave_data-0.1.2 → behave_data-0.1.3}/.github/ISSUE_TEMPLATE/feature_request.yml +0 -0
- {behave_data-0.1.2 → behave_data-0.1.3}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
- {behave_data-0.1.2 → behave_data-0.1.3}/.github/workflows/ci.yml +0 -0
- {behave_data-0.1.2 → behave_data-0.1.3}/.github/workflows/release.yml +0 -0
- {behave_data-0.1.2 → behave_data-0.1.3}/.gitignore +0 -0
- {behave_data-0.1.2 → behave_data-0.1.3}/.pre-commit-config.yaml +0 -0
- {behave_data-0.1.2 → behave_data-0.1.3}/CODE_OF_CONDUCT.md +0 -0
- {behave_data-0.1.2 → behave_data-0.1.3}/CONTRIBUTING.md +0 -0
- {behave_data-0.1.2 → behave_data-0.1.3}/LICENSE +0 -0
- {behave_data-0.1.2 → behave_data-0.1.3}/Makefile +0 -0
- {behave_data-0.1.2 → behave_data-0.1.3}/README.md +0 -0
- {behave_data-0.1.2 → behave_data-0.1.3}/SECURITY.md +0 -0
- {behave_data-0.1.2 → behave_data-0.1.3}/behave_data/builders.py +0 -0
- {behave_data-0.1.2 → behave_data-0.1.3}/behave_data/config.py +0 -0
- {behave_data-0.1.2 → behave_data-0.1.3}/behave_data/diff.py +0 -0
- {behave_data-0.1.2 → behave_data-0.1.3}/behave_data/errors.py +0 -0
- {behave_data-0.1.2 → behave_data-0.1.3}/behave_data/examples.py +0 -0
- {behave_data-0.1.2 → behave_data-0.1.3}/behave_data/fixtures.py +0 -0
- {behave_data-0.1.2 → behave_data-0.1.3}/behave_data/hooks.py +0 -0
- {behave_data-0.1.2 → behave_data-0.1.3}/behave_data/loaders/__init__.py +0 -0
- {behave_data-0.1.2 → behave_data-0.1.3}/behave_data/loaders/csv.py +0 -0
- {behave_data-0.1.2 → behave_data-0.1.3}/behave_data/loaders/excel.py +0 -0
- {behave_data-0.1.2 → behave_data-0.1.3}/behave_data/loaders/http.py +0 -0
- {behave_data-0.1.2 → behave_data-0.1.3}/behave_data/loaders/json.py +0 -0
- {behave_data-0.1.2 → behave_data-0.1.3}/behave_data/loaders/sql.py +0 -0
- {behave_data-0.1.2 → behave_data-0.1.3}/behave_data/loaders/yaml.py +0 -0
- {behave_data-0.1.2 → behave_data-0.1.3}/behave_data/manager.py +0 -0
- {behave_data-0.1.2 → behave_data-0.1.3}/behave_data/null.py +0 -0
- {behave_data-0.1.2 → behave_data-0.1.3}/behave_data/patch.py +0 -0
- {behave_data-0.1.2 → behave_data-0.1.3}/behave_data/py.typed +0 -0
- {behave_data-0.1.2 → behave_data-0.1.3}/behave_data/raw_table.py +0 -0
- {behave_data-0.1.2 → behave_data-0.1.3}/behave_data/secrets.py +0 -0
- {behave_data-0.1.2 → behave_data-0.1.3}/behave_data/typed_table.py +0 -0
- {behave_data-0.1.2 → behave_data-0.1.3}/behave_data/types.py +0 -0
- {behave_data-0.1.2 → behave_data-0.1.3}/tests/conftest.py +0 -0
- {behave_data-0.1.2 → behave_data-0.1.3}/tests/integration/features/data_tables.feature +0 -0
- {behave_data-0.1.2 → behave_data-0.1.3}/tests/integration/features/diff.feature +0 -0
- {behave_data-0.1.2 → behave_data-0.1.3}/tests/integration/features/null_handling.feature +0 -0
- {behave_data-0.1.2 → behave_data-0.1.3}/tests/integration/features/nullable_types.feature +0 -0
- {behave_data-0.1.2 → behave_data-0.1.3}/tests/integration/features/raw_table.feature +0 -0
- {behave_data-0.1.2 → behave_data-0.1.3}/tests/integration/features/typing.feature +0 -0
- {behave_data-0.1.2 → behave_data-0.1.3}/tests/integration/steps/data_tables_steps.py +0 -0
- {behave_data-0.1.2 → behave_data-0.1.3}/tests/integration/test_integration.py +0 -0
- {behave_data-0.1.2 → behave_data-0.1.3}/tests/unit/test_builders.py +0 -0
- {behave_data-0.1.2 → behave_data-0.1.3}/tests/unit/test_config.py +0 -0
- {behave_data-0.1.2 → behave_data-0.1.3}/tests/unit/test_diff.py +0 -0
- {behave_data-0.1.2 → behave_data-0.1.3}/tests/unit/test_errors.py +0 -0
- {behave_data-0.1.2 → behave_data-0.1.3}/tests/unit/test_examples.py +0 -0
- {behave_data-0.1.2 → behave_data-0.1.3}/tests/unit/test_fixtures.py +0 -0
- {behave_data-0.1.2 → behave_data-0.1.3}/tests/unit/test_hooks.py +0 -0
- {behave_data-0.1.2 → behave_data-0.1.3}/tests/unit/test_loaders.py +0 -0
- {behave_data-0.1.2 → behave_data-0.1.3}/tests/unit/test_manager.py +0 -0
- {behave_data-0.1.2 → behave_data-0.1.3}/tests/unit/test_null.py +0 -0
- {behave_data-0.1.2 → behave_data-0.1.3}/tests/unit/test_patch.py +0 -0
- {behave_data-0.1.2 → behave_data-0.1.3}/tests/unit/test_raw_table.py +0 -0
- {behave_data-0.1.2 → behave_data-0.1.3}/tests/unit/test_secrets.py +0 -0
- {behave_data-0.1.2 → behave_data-0.1.3}/tests/unit/test_tags.py +0 -0
- {behave_data-0.1.2 → behave_data-0.1.3}/tests/unit/test_typed_table.py +0 -0
- {behave_data-0.1.2 → behave_data-0.1.3}/tests/unit/test_types.py +0 -0
|
@@ -5,6 +5,25 @@ All notable changes to this project will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [0.1.3] - 2026-07-17
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
|
|
12
|
+
- 75 edge case unit tests covering hooks, secrets, examples, raw_table, yaml, types, diff, fixtures, loaders, config, and manager
|
|
13
|
+
- 4 new E2E Behave integration features: fixtures_builders, secrets, typed_table_edge, dynamic_examples
|
|
14
|
+
- 27 E2E scenarios with 64 steps testing all behave-data features in real Behave
|
|
15
|
+
|
|
16
|
+
### Fixed
|
|
17
|
+
|
|
18
|
+
- `tags.py` now strips `@` prefix from Behave tags (Behave strips `@` from scenario tags)
|
|
19
|
+
- `tags.py` `@needs_data` now sets attribute on context in addition to `_behave_data_loaded`
|
|
20
|
+
- `tags.py` cleanup functions now support both `func(context)` and `func()` signatures
|
|
21
|
+
|
|
22
|
+
### Changed
|
|
23
|
+
|
|
24
|
+
- Coverage increased from 94% to 98%
|
|
25
|
+
- Total tests: 411 passed, 1 skipped (pandas optional)
|
|
26
|
+
|
|
8
27
|
## [0.1.2] - 2026-07-17
|
|
9
28
|
|
|
10
29
|
### Fixed
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: behave-data
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.3
|
|
4
4
|
Summary: Data management for Behave — typed tables, diff, dynamic examples, fixtures, secrets
|
|
5
5
|
Project-URL: Homepage, https://github.com/MathiasPaulenko/behave-data
|
|
6
6
|
Project-URL: Repository, https://github.com/MathiasPaulenko/behave-data
|
|
@@ -38,7 +38,7 @@ from behave_data.tags import process_tags_after_scenario, process_tags_before_sc
|
|
|
38
38
|
from behave_data.typed_table import TypedTableWrapper, typed_wrap
|
|
39
39
|
from behave_data.types import TYPE_CONVERTERS, convert_cell, parse_column_header, register_type
|
|
40
40
|
|
|
41
|
-
__version__ = "0.1.
|
|
41
|
+
__version__ = "0.1.3"
|
|
42
42
|
|
|
43
43
|
__all__ = [
|
|
44
44
|
"BehaveDataError",
|
|
@@ -29,15 +29,17 @@ def process_tags_before_scenario(context: Any, scenario: Any) -> None:
|
|
|
29
29
|
context._behave_data_loaded = {}
|
|
30
30
|
|
|
31
31
|
for tag in tags:
|
|
32
|
-
|
|
33
|
-
|
|
32
|
+
tag = tag.lstrip("@")
|
|
33
|
+
if tag.startswith("needs_data:"):
|
|
34
|
+
name = tag[len("needs_data:") :]
|
|
34
35
|
data = context.data.fixture(name)
|
|
35
36
|
context._behave_data_loaded[name] = data
|
|
36
|
-
|
|
37
|
-
|
|
37
|
+
setattr(context, name, data)
|
|
38
|
+
elif tag.startswith("with_fixture:"):
|
|
39
|
+
name = tag[len("with_fixture:") :]
|
|
38
40
|
data = context.data.fixture(name)
|
|
39
41
|
setattr(context, name, data)
|
|
40
|
-
elif tag == "
|
|
42
|
+
elif tag == "cleanup_after":
|
|
41
43
|
context._behave_data_cleanup = True
|
|
42
44
|
if not hasattr(context, "_behave_data_cleanup_funcs"):
|
|
43
45
|
context._behave_data_cleanup_funcs = []
|
|
@@ -58,6 +60,9 @@ def process_tags_after_scenario(context: Any, scenario: Any) -> None:
|
|
|
58
60
|
|
|
59
61
|
funcs = getattr(context, "_behave_data_cleanup_funcs", [])
|
|
60
62
|
for func in funcs:
|
|
61
|
-
|
|
63
|
+
try:
|
|
64
|
+
func(context)
|
|
65
|
+
except TypeError:
|
|
66
|
+
func()
|
|
62
67
|
|
|
63
68
|
context._behave_data_cleanup = False
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
"""Behave environment for integration tests."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from behave_data import (
|
|
6
|
+
after_scenario_hook,
|
|
7
|
+
before_feature_hook,
|
|
8
|
+
before_scenario_hook,
|
|
9
|
+
before_step_hook,
|
|
10
|
+
setup_data,
|
|
11
|
+
)
|
|
12
|
+
from behave_data.builders import data_builder
|
|
13
|
+
from behave_data.fixtures import data_fixture
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
@data_fixture("admin_user")
|
|
17
|
+
def admin_user():
|
|
18
|
+
return {"name": "Admin", "role": "admin", "email": "admin@test.com"}
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
@data_fixture("regular_user", params=["alice", "bob"])
|
|
22
|
+
def regular_user(param):
|
|
23
|
+
return {"name": param, "role": "user"}
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
@data_builder("product")
|
|
27
|
+
def product(overrides):
|
|
28
|
+
return {"name": "Widget", "price": 9.99, **overrides}
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def before_all(context):
|
|
32
|
+
setup_data(context)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def before_feature(context, feature):
|
|
36
|
+
before_feature_hook(context, feature)
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def before_scenario(context, scenario):
|
|
40
|
+
before_scenario_hook(context, scenario)
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def before_step(context, step):
|
|
44
|
+
before_step_hook(context, step)
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def after_scenario(context, scenario):
|
|
48
|
+
after_scenario_hook(context, scenario)
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
Feature: Dynamic examples E2E
|
|
2
|
+
|
|
3
|
+
@load_examples:csv:features/data/users.csv
|
|
4
|
+
Scenario Outline: Load users from CSV
|
|
5
|
+
Given a user with name "<name>" and email "<email>"
|
|
6
|
+
Then the user "<name>" should have email "<email>"
|
|
7
|
+
|
|
8
|
+
Examples:
|
|
9
|
+
| name | email |
|
|
10
|
+
| old | old |
|
|
11
|
+
|
|
12
|
+
@load_examples:json:features/data/products.json
|
|
13
|
+
Scenario Outline: Load products from JSON
|
|
14
|
+
Given a product with name "<name>" and price "<price>"
|
|
15
|
+
Then the product "<name>" should cost "<price>"
|
|
16
|
+
|
|
17
|
+
Examples:
|
|
18
|
+
| name | price |
|
|
19
|
+
| old | old |
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
Feature: Fixtures and builders E2E
|
|
2
|
+
|
|
3
|
+
@needs_data:admin_user
|
|
4
|
+
Scenario: Admin user fixture is available via needs_data tag
|
|
5
|
+
Given I need an admin user
|
|
6
|
+
Then the admin user should have role "admin"
|
|
7
|
+
And the admin user should have email "admin@test.com"
|
|
8
|
+
|
|
9
|
+
@needs_data:regular_user:alice
|
|
10
|
+
Scenario: Parametrized fixture is available via needs_data tag
|
|
11
|
+
Given I need a regular user
|
|
12
|
+
Then the regular user should have name "alice"
|
|
13
|
+
And the regular user should have role "user"
|
|
14
|
+
|
|
15
|
+
Scenario: Build a single product via DataManager
|
|
16
|
+
Given I build a product
|
|
17
|
+
Then the product should have name "Widget"
|
|
18
|
+
And the product should have price 9.99
|
|
19
|
+
|
|
20
|
+
Scenario: Build a product with overrides
|
|
21
|
+
Given I build a product with name "Gadget" and price 19.99
|
|
22
|
+
Then the product should have name "Gadget"
|
|
23
|
+
And the product should have price 19.99
|
|
24
|
+
|
|
25
|
+
Scenario: Build multiple products
|
|
26
|
+
Given I build 3 products
|
|
27
|
+
Then I should have 3 products
|
|
28
|
+
And each product should have name "Widget"
|
|
29
|
+
|
|
30
|
+
@cleanup_after
|
|
31
|
+
Scenario: Cleanup after scenario
|
|
32
|
+
Given I register a cleanup function
|
|
33
|
+
Then the cleanup flag should be True
|
|
34
|
+
# After scenario, cleanup runs automatically via after_scenario_hook
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
Feature: Secrets and placeholder resolution E2E
|
|
2
|
+
|
|
3
|
+
Scenario: Resolve env placeholder in step
|
|
4
|
+
Given I set environment variable "TEST_SECRET" to "my_secret_value"
|
|
5
|
+
When I resolve the placeholder "env:TEST_SECRET"
|
|
6
|
+
Then the resolved value should be "my_secret_value"
|
|
7
|
+
|
|
8
|
+
Scenario: Resolve plain text passthrough
|
|
9
|
+
When I resolve the placeholder "just_plain_text"
|
|
10
|
+
Then the resolved value should be "just_plain_text"
|
|
11
|
+
|
|
12
|
+
Scenario: Mask a resolved env value
|
|
13
|
+
Given I set environment variable "API_TOKEN" to "secret_token_123"
|
|
14
|
+
When I resolve and mask the placeholder "env:API_TOKEN"
|
|
15
|
+
Then the masked value should be "secret_token_123"
|
|
16
|
+
|
|
17
|
+
Scenario: Resolve integer passthrough
|
|
18
|
+
When I resolve the value 42
|
|
19
|
+
Then the resolved value should be "42"
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
Feature: Typed table edge cases E2E
|
|
2
|
+
|
|
3
|
+
Scenario: All supported types in one table
|
|
4
|
+
Given a table with all types
|
|
5
|
+
| name:str | age:int | price:float | active:bool | created:date |
|
|
6
|
+
| Alice | 30 | 99.99 | true | 2024-01-15 |
|
|
7
|
+
| Bob | 25 | 49.50 | false | 2024-06-30 |
|
|
8
|
+
Then all types should be correctly converted
|
|
9
|
+
| name:str | age:int | price:float | active:bool | created:date |
|
|
10
|
+
| Alice | 30 | 99.99 | true | 2024-01-15 |
|
|
11
|
+
| Bob | 25 | 49.50 | false | 2024-06-30 |
|
|
12
|
+
|
|
13
|
+
Scenario: Nullable columns with null markers
|
|
14
|
+
Given a table with nullable typed columns
|
|
15
|
+
| name:str | age:int? | city:str? |
|
|
16
|
+
| Alice | 30 | NYC |
|
|
17
|
+
| Bob | null | N/A |
|
|
18
|
+
| Carol | | |
|
|
19
|
+
Then nullable columns should resolve nulls to None
|
|
20
|
+
|
|
21
|
+
Scenario: Empty table with typed headers
|
|
22
|
+
Given an empty typed table
|
|
23
|
+
| name:str | age:int |
|
|
24
|
+
Then the typed table should have 0 data rows
|
|
25
|
+
|
|
26
|
+
Scenario: Single column table
|
|
27
|
+
Given a single column table
|
|
28
|
+
| name:str |
|
|
29
|
+
| Alice |
|
|
30
|
+
Then the single column should have one entry "Alice"
|
|
31
|
+
|
|
32
|
+
Scenario: Table with special characters
|
|
33
|
+
Given a table with special characters
|
|
34
|
+
| name:str | description:str |
|
|
35
|
+
| Alice | A "quoted" value |
|
|
36
|
+
| Bob | A 'single' value |
|
|
37
|
+
Then special characters should be preserved
|
|
@@ -0,0 +1,247 @@
|
|
|
1
|
+
"""E2E steps for fixtures, builders, secrets, typed tables, and dynamic examples."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import os
|
|
6
|
+
from datetime import date
|
|
7
|
+
|
|
8
|
+
from behave import given, then, when
|
|
9
|
+
|
|
10
|
+
from behave_data import typed_wrap
|
|
11
|
+
|
|
12
|
+
# --------------------------------------------------------------------------- #
|
|
13
|
+
# Fixtures & Builders
|
|
14
|
+
# --------------------------------------------------------------------------- #
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
@given("I need an admin user")
|
|
18
|
+
def step_need_admin(context):
|
|
19
|
+
assert hasattr(context, "admin_user")
|
|
20
|
+
context.current_user = context.admin_user
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
@then('the admin user should have role "{role}"')
|
|
24
|
+
def step_admin_role(context, role):
|
|
25
|
+
assert context.current_user["role"] == role
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
@then('the admin user should have email "{email}"')
|
|
29
|
+
def step_admin_email(context, email):
|
|
30
|
+
assert context.current_user["email"] == email
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
@given("I need a regular user")
|
|
34
|
+
def step_need_regular(context):
|
|
35
|
+
loaded = getattr(context, "_behave_data_loaded", {})
|
|
36
|
+
# Find any regular_user:* fixture that was loaded
|
|
37
|
+
for key, val in loaded.items():
|
|
38
|
+
if key.startswith("regular_user:"):
|
|
39
|
+
context.current_user = val
|
|
40
|
+
return
|
|
41
|
+
raise AssertionError("No regular_user fixture loaded")
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
@then('the regular user should have name "{name}"')
|
|
45
|
+
def step_regular_name(context, name):
|
|
46
|
+
assert context.current_user["name"] == name
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
@then('the regular user should have role "{role}"')
|
|
50
|
+
def step_regular_role(context, role):
|
|
51
|
+
assert context.current_user["role"] == role
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
@given("I build a product")
|
|
55
|
+
def step_build_product(context):
|
|
56
|
+
context.product = context.data.build("product")
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
@then('the product should have name "{name}"')
|
|
60
|
+
def step_product_name(context, name):
|
|
61
|
+
assert context.product["name"] == name
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
@then("the product should have price {price:f}")
|
|
65
|
+
def step_product_price(context, price):
|
|
66
|
+
assert abs(context.product["price"] - price) < 0.01
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
@given('I build a product with name "{name}" and price {price:f}')
|
|
70
|
+
def step_build_product_overrides(context, name, price):
|
|
71
|
+
context.product = context.data.build("product", overrides={"name": name, "price": price})
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
@given("I build {n:d} products")
|
|
75
|
+
def step_build_multiple(context, n):
|
|
76
|
+
context.products = context.data.build("product", count=n)
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
@then("I should have {n:d} products")
|
|
80
|
+
def step_count_products(context, n):
|
|
81
|
+
assert len(context.products) == n
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
@then('each product should have name "{name}"')
|
|
85
|
+
def step_each_product_name(context, name):
|
|
86
|
+
for p in context.products:
|
|
87
|
+
assert p["name"] == name
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
@given("I register a cleanup function")
|
|
91
|
+
def step_register_cleanup(context):
|
|
92
|
+
context.cleanup_flag = False
|
|
93
|
+
|
|
94
|
+
def _cleanup():
|
|
95
|
+
context.cleanup_flag = False
|
|
96
|
+
|
|
97
|
+
if not hasattr(context, "_behave_data_cleanup_funcs"):
|
|
98
|
+
context._behave_data_cleanup_funcs = []
|
|
99
|
+
context._behave_data_cleanup_funcs.append(_cleanup)
|
|
100
|
+
context.cleanup_flag = True
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
@then("the cleanup flag should be True")
|
|
104
|
+
def step_cleanup_flag(context):
|
|
105
|
+
assert context.cleanup_flag is True
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
# --------------------------------------------------------------------------- #
|
|
109
|
+
# Secrets
|
|
110
|
+
# --------------------------------------------------------------------------- #
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
@given('I set environment variable "{var}" to "{value}"')
|
|
114
|
+
def step_set_env(context, var, value):
|
|
115
|
+
os.environ[var] = value
|
|
116
|
+
context._env_var = var
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
@when('I resolve the placeholder "{placeholder}"')
|
|
120
|
+
def step_resolve(context, placeholder):
|
|
121
|
+
context.resolved = context.data.resolve(placeholder)
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
@then('the resolved value should be "{value}"')
|
|
125
|
+
def step_resolved_value(context, value):
|
|
126
|
+
assert str(context.resolved) == value
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
@when('I resolve and mask the placeholder "{placeholder}"')
|
|
130
|
+
def step_resolve_and_mask(context, placeholder):
|
|
131
|
+
raw = context.data.resolve(placeholder)
|
|
132
|
+
context.resolved = raw
|
|
133
|
+
context.masked = context.data.mask(raw)
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
@then('the masked value should be "{value}"')
|
|
137
|
+
def step_masked_value(context, value):
|
|
138
|
+
assert str(context.masked) == value
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
@when("I resolve the value {val:d}")
|
|
142
|
+
def step_resolve_int(context, val):
|
|
143
|
+
context.resolved = context.data.resolve(val)
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
# --------------------------------------------------------------------------- #
|
|
147
|
+
# Typed table edge cases
|
|
148
|
+
# --------------------------------------------------------------------------- #
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
@given("a table with all types")
|
|
152
|
+
def step_all_types(context):
|
|
153
|
+
context.typed = typed_wrap(context.table).typed_dicts()
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
@then("all types should be correctly converted")
|
|
157
|
+
def step_check_all_types(context):
|
|
158
|
+
expected = typed_wrap(context.table).typed_dicts()
|
|
159
|
+
assert len(expected) == 2
|
|
160
|
+
assert isinstance(expected[0]["name"], str)
|
|
161
|
+
assert isinstance(expected[0]["age"], int)
|
|
162
|
+
assert isinstance(expected[0]["price"], float)
|
|
163
|
+
assert isinstance(expected[0]["active"], bool)
|
|
164
|
+
assert isinstance(expected[0]["created"], date)
|
|
165
|
+
assert expected[0]["active"] is True
|
|
166
|
+
assert expected[1]["active"] is False
|
|
167
|
+
assert expected[0]["created"] == date(2024, 1, 15)
|
|
168
|
+
assert expected[1]["created"] == date(2024, 6, 30)
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
@given("a table with nullable typed columns")
|
|
172
|
+
def step_nullable_typed(context):
|
|
173
|
+
context.typed = typed_wrap(context.table).typed_dicts()
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
@then("nullable columns should resolve nulls to None")
|
|
177
|
+
def step_check_nullable(context):
|
|
178
|
+
rows = context.typed
|
|
179
|
+
assert rows[0]["age"] == 30
|
|
180
|
+
assert rows[0]["city"] == "NYC"
|
|
181
|
+
assert rows[1]["age"] is None
|
|
182
|
+
assert rows[1]["city"] is None
|
|
183
|
+
assert rows[2]["age"] is None
|
|
184
|
+
assert rows[2]["city"] is None
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
@given("an empty typed table")
|
|
188
|
+
def step_empty_typed(context):
|
|
189
|
+
context.typed = typed_wrap(context.table)
|
|
190
|
+
|
|
191
|
+
|
|
192
|
+
@then("the typed table should have 0 data rows")
|
|
193
|
+
def step_zero_rows(context):
|
|
194
|
+
assert len(context.typed.typed_dicts()) == 0
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
@given("a single column table")
|
|
198
|
+
def step_single_col(context):
|
|
199
|
+
context.typed = typed_wrap(context.table).typed_dicts()
|
|
200
|
+
|
|
201
|
+
|
|
202
|
+
@then('the single column should have one entry "{value}"')
|
|
203
|
+
def step_single_entry(context, value):
|
|
204
|
+
rows = context.typed
|
|
205
|
+
assert len(rows) == 1
|
|
206
|
+
assert rows[0]["name"] == value
|
|
207
|
+
|
|
208
|
+
|
|
209
|
+
@given("a table with special characters")
|
|
210
|
+
def step_special_chars(context):
|
|
211
|
+
context.typed = typed_wrap(context.table).typed_dicts()
|
|
212
|
+
|
|
213
|
+
|
|
214
|
+
@then("special characters should be preserved")
|
|
215
|
+
def step_check_special(context):
|
|
216
|
+
rows = context.typed
|
|
217
|
+
assert "quoted" in rows[0]["description"]
|
|
218
|
+
assert "single" in rows[1]["description"]
|
|
219
|
+
|
|
220
|
+
|
|
221
|
+
# --------------------------------------------------------------------------- #
|
|
222
|
+
# Dynamic examples
|
|
223
|
+
# --------------------------------------------------------------------------- #
|
|
224
|
+
|
|
225
|
+
|
|
226
|
+
@given('a user with name "{name}" and email "{email}"')
|
|
227
|
+
def step_user_with(context, name, email):
|
|
228
|
+
context.current_name = name
|
|
229
|
+
context.current_email = email
|
|
230
|
+
|
|
231
|
+
|
|
232
|
+
@then('the user "{name}" should have email "{email}"')
|
|
233
|
+
def step_user_email(context, name, email):
|
|
234
|
+
assert context.current_name == name
|
|
235
|
+
assert context.current_email == email
|
|
236
|
+
|
|
237
|
+
|
|
238
|
+
@given('a product with name "{name}" and price "{price}"')
|
|
239
|
+
def step_product_with(context, name, price):
|
|
240
|
+
context.current_product_name = name
|
|
241
|
+
context.current_product_price = price
|
|
242
|
+
|
|
243
|
+
|
|
244
|
+
@then('the product "{name}" should cost "{price}"')
|
|
245
|
+
def step_product_cost(context, name, price):
|
|
246
|
+
assert context.current_product_name == name
|
|
247
|
+
assert context.current_product_price == price
|