recce-nightly 0.62.0.20250417__py3-none-any.whl → 1.30.0.20251221__py3-none-any.whl
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.
Potentially problematic release.
This version of recce-nightly might be problematic. Click here for more details.
- recce/VERSION +1 -1
- recce/__init__.py +27 -22
- recce/adapter/base.py +11 -14
- recce/adapter/dbt_adapter/__init__.py +845 -461
- recce/adapter/dbt_adapter/dbt_version.py +3 -0
- recce/adapter/sqlmesh_adapter.py +24 -35
- recce/apis/check_api.py +59 -42
- recce/apis/check_events_api.py +353 -0
- recce/apis/check_func.py +41 -35
- recce/apis/run_api.py +25 -19
- recce/apis/run_func.py +64 -25
- recce/artifact.py +119 -51
- recce/cli.py +1301 -324
- recce/config.py +43 -34
- recce/connect_to_cloud.py +138 -0
- recce/core.py +55 -47
- recce/data/404/index.html +2 -0
- recce/data/404.html +2 -1
- recce/data/__next.@lineage.!KHNsb3Qp.__PAGE__.txt +7 -0
- recce/data/__next.@lineage.!KHNsb3Qp.txt +4 -0
- recce/data/__next.__PAGE__.txt +6 -0
- recce/data/__next._full.txt +32 -0
- recce/data/__next._head.txt +8 -0
- recce/data/__next._index.txt +14 -0
- recce/data/__next._tree.txt +8 -0
- recce/data/_next/static/chunks/025a7e3e3f9f40ae.js +1 -0
- recce/data/_next/static/chunks/0ce56d67ef5779ca.js +4 -0
- recce/data/_next/static/chunks/1a6a78780155dac7.js +48 -0
- recce/data/_next/static/chunks/1de8485918b9182a.css +2 -0
- recce/data/_next/static/chunks/1e4b1b50d1e34993.js +1 -0
- recce/data/_next/static/chunks/206d5d181e4c738e.js +1 -0
- recce/data/_next/static/chunks/2c357efc34c5b859.js +25 -0
- recce/data/_next/static/chunks/2e9d95d2d48c479c.js +1 -0
- recce/data/_next/static/chunks/2f016dc4a3edad2e.js +2 -0
- recce/data/_next/static/chunks/313251962d698f7c.js +1 -0
- recce/data/_next/static/chunks/3a9f021f38eb5574.css +1 -0
- recce/data/_next/static/chunks/40079da8d2b8f651.js +1 -0
- recce/data/_next/static/chunks/4599182bffb64661.js +38 -0
- recce/data/_next/static/chunks/4e62f6e184173580.js +1 -0
- recce/data/_next/static/chunks/5c4dfb0d09eaa401.js +1 -0
- recce/data/_next/static/chunks/69e4f06ccfdfc3ac.js +1 -0
- recce/data/_next/static/chunks/6b206cb4707d6bee.js +1 -0
- recce/data/_next/static/chunks/6d8557f062aa4386.css +1 -0
- recce/data/_next/static/chunks/7fbe3650bd83b6b5.js +1 -0
- recce/data/_next/static/chunks/83fa823a825674f6.js +1 -0
- recce/data/_next/static/chunks/848a6c9b5f55f7ed.js +1 -0
- recce/data/_next/static/chunks/859462b0858aef88.css +2 -0
- recce/data/_next/static/chunks/923964f18c87d0f1.css +1 -0
- recce/data/_next/static/chunks/939390f911895d7c.js +48 -0
- recce/data/_next/static/chunks/99a9817237a07f43.js +1 -0
- recce/data/_next/static/chunks/9fed8b4b2b924054.js +5 -0
- recce/data/_next/static/chunks/b6949f6c5892110c.js +1 -0
- recce/data/_next/static/chunks/b851a1d3f8149828.js +1 -0
- recce/data/_next/static/chunks/c734f9ad957de0b4.js +1 -0
- recce/data/_next/static/chunks/cdde321b0ec75717.js +2 -0
- recce/data/_next/static/chunks/d0f91117d77ff844.css +1 -0
- recce/data/_next/static/chunks/d6c8667911c2500f.js +1 -0
- recce/data/_next/static/chunks/da8dab68c02752cf.js +74 -0
- recce/data/_next/static/chunks/dc074049c9d12d97.js +109 -0
- recce/data/_next/static/chunks/ee7f1a8227342421.js +1 -0
- recce/data/_next/static/chunks/fa2f4e56c2fccc73.js +1 -0
- recce/data/_next/static/chunks/turbopack-1fad664f62979b93.js +3 -0
- recce/data/_next/static/media/favicon.a8d38d84.ico +0 -0
- recce/data/_next/static/media/montserrat-cyrillic-800-normal.d80d830d.woff2 +0 -0
- recce/data/_next/static/media/montserrat-cyrillic-800-normal.f9d58125.woff +0 -0
- recce/data/_next/static/media/montserrat-cyrillic-ext-800-normal.076c2a93.woff2 +0 -0
- recce/data/_next/static/media/montserrat-cyrillic-ext-800-normal.a4fa76b5.woff +0 -0
- recce/data/_next/static/media/montserrat-latin-800-normal.cde454cc.woff2 +0 -0
- recce/data/_next/static/media/montserrat-latin-800-normal.d5761935.woff +0 -0
- recce/data/_next/static/media/montserrat-latin-ext-800-normal.40ec0659.woff2 +0 -0
- recce/data/_next/static/media/montserrat-latin-ext-800-normal.b671449b.woff +0 -0
- recce/data/_next/static/media/montserrat-vietnamese-800-normal.9f7b8541.woff +0 -0
- recce/data/_next/static/media/montserrat-vietnamese-800-normal.f9eb854e.woff2 +0 -0
- recce/data/_next/static/nX-Uz0AH6Tc6hIQUFGqaB/_buildManifest.js +11 -0
- recce/data/_next/static/nX-Uz0AH6Tc6hIQUFGqaB/_clientMiddlewareManifest.json +1 -0
- recce/data/_not-found/__next._full.txt +24 -0
- recce/data/_not-found/__next._head.txt +8 -0
- recce/data/_not-found/__next._index.txt +13 -0
- recce/data/_not-found/__next._not-found.__PAGE__.txt +5 -0
- recce/data/_not-found/__next._not-found.txt +4 -0
- recce/data/_not-found/__next._tree.txt +6 -0
- recce/data/_not-found/index.html +2 -0
- recce/data/_not-found/index.txt +24 -0
- recce/data/auth_callback.html +68 -0
- recce/data/checks/__next.@lineage.__DEFAULT__.txt +7 -0
- recce/data/checks/__next._full.txt +39 -0
- recce/data/checks/__next._head.txt +8 -0
- recce/data/checks/__next._index.txt +14 -0
- recce/data/checks/__next._tree.txt +8 -0
- recce/data/checks/__next.checks.__PAGE__.txt +10 -0
- recce/data/checks/__next.checks.txt +4 -0
- recce/data/checks/index.html +2 -0
- recce/data/checks/index.txt +39 -0
- recce/data/imgs/reload-image.svg +4 -0
- recce/data/index.html +2 -27
- recce/data/index.txt +32 -7
- recce/data/lineage/__next.@lineage.__DEFAULT__.txt +7 -0
- recce/data/lineage/__next._full.txt +39 -0
- recce/data/lineage/__next._head.txt +8 -0
- recce/data/lineage/__next._index.txt +14 -0
- recce/data/lineage/__next._tree.txt +8 -0
- recce/data/lineage/__next.lineage.__PAGE__.txt +10 -0
- recce/data/lineage/__next.lineage.txt +4 -0
- recce/data/lineage/index.html +2 -0
- recce/data/lineage/index.txt +39 -0
- recce/data/query/__next.@lineage.__DEFAULT__.txt +7 -0
- recce/data/query/__next._full.txt +37 -0
- recce/data/query/__next._head.txt +8 -0
- recce/data/query/__next._index.txt +14 -0
- recce/data/query/__next._tree.txt +8 -0
- recce/data/query/__next.query.__PAGE__.txt +9 -0
- recce/data/query/__next.query.txt +4 -0
- recce/data/query/index.html +2 -0
- recce/data/query/index.txt +37 -0
- recce/diff.py +6 -12
- recce/event/CONFIG.bak +1 -0
- recce/event/__init__.py +86 -74
- recce/event/collector.py +33 -22
- recce/event/track.py +49 -27
- recce/exceptions.py +1 -1
- recce/git.py +7 -7
- recce/github.py +57 -53
- recce/mcp_server.py +725 -0
- recce/models/__init__.py +4 -1
- recce/models/check.py +438 -21
- recce/models/run.py +1 -0
- recce/models/types.py +134 -28
- recce/pull_request.py +27 -25
- recce/run.py +179 -122
- recce/server.py +394 -104
- recce/state/__init__.py +31 -0
- recce/state/cloud.py +644 -0
- recce/state/const.py +26 -0
- recce/state/local.py +56 -0
- recce/state/state.py +119 -0
- recce/state/state_loader.py +174 -0
- recce/summary.py +196 -149
- recce/tasks/__init__.py +19 -3
- recce/tasks/core.py +11 -13
- recce/tasks/dataframe.py +82 -18
- recce/tasks/histogram.py +69 -34
- recce/tasks/lineage.py +2 -2
- recce/tasks/profile.py +152 -86
- recce/tasks/query.py +180 -89
- recce/tasks/rowcount.py +37 -31
- recce/tasks/schema.py +18 -15
- recce/tasks/top_k.py +35 -35
- recce/tasks/utils.py +147 -0
- recce/tasks/valuediff.py +247 -155
- recce/util/__init__.py +3 -0
- recce/util/api_token.py +80 -0
- recce/util/breaking.py +105 -100
- recce/util/cll.py +274 -219
- recce/util/cloud/__init__.py +15 -0
- recce/util/cloud/base.py +115 -0
- recce/util/cloud/check_events.py +190 -0
- recce/util/cloud/checks.py +242 -0
- recce/util/io.py +22 -17
- recce/util/lineage.py +65 -16
- recce/util/logger.py +1 -1
- recce/util/onboarding_state.py +45 -0
- recce/util/perf_tracking.py +85 -0
- recce/util/recce_cloud.py +347 -72
- recce/util/singleton.py +4 -4
- recce/util/startup_perf.py +121 -0
- recce/yaml/__init__.py +7 -10
- recce_nightly-1.30.0.20251221.dist-info/METADATA +195 -0
- recce_nightly-1.30.0.20251221.dist-info/RECORD +183 -0
- {recce_nightly-0.62.0.20250417.dist-info → recce_nightly-1.30.0.20251221.dist-info}/WHEEL +1 -2
- recce/data/_next/static/chunks/1f229bf6-d9fe92e56db8d93b.js +0 -1
- recce/data/_next/static/chunks/29e3cc0d-8c150e37dff9631b.js +0 -1
- recce/data/_next/static/chunks/36e1c10d-bb0210cbd6573a8d.js +0 -1
- recce/data/_next/static/chunks/3998a672-eaad84bdd88cc73e.js +0 -1
- recce/data/_next/static/chunks/450c323b-1bb5db526e54435a.js +0 -1
- recce/data/_next/static/chunks/47d8844f-79a1b53c66a7d7ec.js +0 -1
- recce/data/_next/static/chunks/500-e51c92a025a51234.js +0 -65
- recce/data/_next/static/chunks/6dc81886-c94b9b91bc2c3caf.js +0 -1
- recce/data/_next/static/chunks/700-3b65fc3666820d00.js +0 -2
- recce/data/_next/static/chunks/7a8a3e83-d7fa409d97b38b2b.js +0 -1
- recce/data/_next/static/chunks/7f27ae6c-413f6b869a04183a.js +0 -1
- recce/data/_next/static/chunks/9746af58-d74bef4d03eea6ab.js +0 -1
- recce/data/_next/static/chunks/a30376cd-7d806e1602f2dc3a.js +0 -1
- recce/data/_next/static/chunks/app/_not-found/page-8a886fa0855c3105.js +0 -1
- recce/data/_next/static/chunks/app/layout-9102e22cb73f74d6.js +0 -1
- recce/data/_next/static/chunks/app/page-9adc25782272ed2e.js +0 -1
- recce/data/_next/static/chunks/b63b1b3f-7395c74e11a14e95.js +0 -1
- recce/data/_next/static/chunks/c132bf7d-8102037f9ccf372a.js +0 -1
- recce/data/_next/static/chunks/c1ceaa8b-a1e442154d23515e.js +0 -1
- recce/data/_next/static/chunks/cd9f8d63-cf0d5a7b0f7a92e8.js +0 -54
- recce/data/_next/static/chunks/ce84277d-f42c2c58049cea2d.js +0 -1
- recce/data/_next/static/chunks/e24bf851-0f8cbc99656833e7.js +0 -1
- recce/data/_next/static/chunks/fee69bc6-f17d36c080742e74.js +0 -1
- recce/data/_next/static/chunks/framework-ded83d71b51ce901.js +0 -1
- recce/data/_next/static/chunks/main-a0859f1f36d0aa6c.js +0 -1
- recce/data/_next/static/chunks/main-app-0225a2255968e566.js +0 -1
- recce/data/_next/static/chunks/pages/_app-d5672bf3d8b6371b.js +0 -1
- recce/data/_next/static/chunks/pages/_error-ed75be3f25588548.js +0 -1
- recce/data/_next/static/chunks/webpack-567d72f0bc0820d5.js +0 -1
- recce/data/_next/static/css/c9ecb46a4b21c126.css +0 -14
- recce/data/_next/static/media/montserrat-cyrillic-800-normal.22628180.woff2 +0 -0
- recce/data/_next/static/media/montserrat-cyrillic-800-normal.31d693bb.woff +0 -0
- recce/data/_next/static/media/montserrat-cyrillic-ext-800-normal.7e2c1e62.woff +0 -0
- recce/data/_next/static/media/montserrat-cyrillic-ext-800-normal.94a63aea.woff2 +0 -0
- recce/data/_next/static/media/montserrat-latin-800-normal.6f8fa298.woff2 +0 -0
- recce/data/_next/static/media/montserrat-latin-800-normal.97e20d5e.woff +0 -0
- recce/data/_next/static/media/montserrat-latin-ext-800-normal.013b84f9.woff2 +0 -0
- recce/data/_next/static/media/montserrat-latin-ext-800-normal.aff52ab0.woff +0 -0
- recce/data/_next/static/media/montserrat-vietnamese-800-normal.5f21869b.woff +0 -0
- recce/data/_next/static/media/montserrat-vietnamese-800-normal.c0035377.woff2 +0 -0
- recce/data/_next/static/qiyFlux77VkhxiceAJe_F/_buildManifest.js +0 -1
- recce/state.py +0 -753
- recce_nightly-0.62.0.20250417.dist-info/METADATA +0 -311
- recce_nightly-0.62.0.20250417.dist-info/RECORD +0 -139
- recce_nightly-0.62.0.20250417.dist-info/top_level.txt +0 -2
- tests/__init__.py +0 -0
- tests/adapter/__init__.py +0 -0
- tests/adapter/dbt_adapter/__init__.py +0 -0
- tests/adapter/dbt_adapter/conftest.py +0 -13
- tests/adapter/dbt_adapter/dbt_test_helper.py +0 -283
- tests/adapter/dbt_adapter/test_dbt_adapter.py +0 -40
- tests/adapter/dbt_adapter/test_dbt_cll.py +0 -102
- tests/adapter/dbt_adapter/test_selector.py +0 -177
- tests/tasks/__init__.py +0 -0
- tests/tasks/conftest.py +0 -4
- tests/tasks/test_histogram.py +0 -137
- tests/tasks/test_lineage.py +0 -42
- tests/tasks/test_preset_checks.py +0 -50
- tests/tasks/test_profile.py +0 -73
- tests/tasks/test_query.py +0 -151
- tests/tasks/test_row_count.py +0 -116
- tests/tasks/test_schema.py +0 -99
- tests/tasks/test_top_k.py +0 -73
- tests/tasks/test_valuediff.py +0 -74
- tests/test_cli.py +0 -122
- tests/test_config.py +0 -45
- tests/test_core.py +0 -27
- tests/test_dbt.py +0 -36
- tests/test_pull_request.py +0 -130
- tests/test_server.py +0 -98
- tests/test_state.py +0 -123
- tests/test_summary.py +0 -57
- /recce/data/_next/static/chunks/{polyfills-42372ed130431b0a.js → a6dad97d9634a72d.js} +0 -0
- /recce/data/_next/static/{qiyFlux77VkhxiceAJe_F → nX-Uz0AH6Tc6hIQUFGqaB}/_ssgManifest.js +0 -0
- {recce_nightly-0.62.0.20250417.dist-info → recce_nightly-1.30.0.20251221.dist-info}/entry_points.txt +0 -0
- {recce_nightly-0.62.0.20250417.dist-info → recce_nightly-1.30.0.20251221.dist-info}/licenses/LICENSE +0 -0
tests/tasks/test_top_k.py
DELETED
|
@@ -1,73 +0,0 @@
|
|
|
1
|
-
import pytest
|
|
2
|
-
|
|
3
|
-
from recce.tasks import TopKDiffTask
|
|
4
|
-
from recce.tasks.top_k import TopKDiffCheckValidator
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
def test_top_k(dbt_test_helper):
|
|
8
|
-
csv_data_curr = """
|
|
9
|
-
customer_id,name,age
|
|
10
|
-
1,Alice,30
|
|
11
|
-
2,Bob,25
|
|
12
|
-
3,Charlie,35
|
|
13
|
-
4,Bob,35
|
|
14
|
-
"""
|
|
15
|
-
|
|
16
|
-
csv_data_base = """
|
|
17
|
-
customer_id,name,age
|
|
18
|
-
1,Alice,35
|
|
19
|
-
2,Bob,25
|
|
20
|
-
3,Charlie,35
|
|
21
|
-
4,,35
|
|
22
|
-
"""
|
|
23
|
-
|
|
24
|
-
dbt_test_helper.create_model("customers", csv_data_base, csv_data_curr)
|
|
25
|
-
|
|
26
|
-
params = dict(
|
|
27
|
-
model="customers",
|
|
28
|
-
column_name="name",
|
|
29
|
-
k=50,
|
|
30
|
-
)
|
|
31
|
-
|
|
32
|
-
task = TopKDiffTask(params)
|
|
33
|
-
run_result = task.execute()
|
|
34
|
-
|
|
35
|
-
# {
|
|
36
|
-
# 'values': ['Bob', 'Alice', 'Charlie'],
|
|
37
|
-
# 'counts': [2, 1, 1],
|
|
38
|
-
# 'valids': 4,
|
|
39
|
-
# 'total': 4
|
|
40
|
-
# }
|
|
41
|
-
assert run_result['current']['values'][0] == 'Bob'
|
|
42
|
-
assert run_result['current']['counts'][0] == 2
|
|
43
|
-
assert run_result['current']['valids'] == 4
|
|
44
|
-
assert run_result['current']['total'] == 4
|
|
45
|
-
|
|
46
|
-
# {
|
|
47
|
-
# 'values': ['Bob', 'Alice', 'Charlie'],
|
|
48
|
-
# 'counts': [1, 1, 1],
|
|
49
|
-
# 'valids': 3,
|
|
50
|
-
# 'total': 4
|
|
51
|
-
# }
|
|
52
|
-
assert run_result['base']['counts'][0] == 1
|
|
53
|
-
assert run_result['base']['valids'] == 3
|
|
54
|
-
assert run_result['base']['total'] == 4
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
def test_validator():
|
|
58
|
-
def validate(params: dict = {}, view_options: dict = {}):
|
|
59
|
-
TopKDiffCheckValidator().validate({
|
|
60
|
-
'name': 'test',
|
|
61
|
-
'type': 'top_k_diff',
|
|
62
|
-
'params': params,
|
|
63
|
-
'view_options': view_options,
|
|
64
|
-
})
|
|
65
|
-
|
|
66
|
-
validate({
|
|
67
|
-
"model": "customers",
|
|
68
|
-
"column_name": "name",
|
|
69
|
-
"k": 50,
|
|
70
|
-
})
|
|
71
|
-
|
|
72
|
-
with pytest.raises(ValueError):
|
|
73
|
-
validate({})
|
tests/tasks/test_valuediff.py
DELETED
|
@@ -1,74 +0,0 @@
|
|
|
1
|
-
import pytest
|
|
2
|
-
|
|
3
|
-
from recce.tasks import ValueDiffTask, ValueDiffDetailTask
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
def test_value_diff(dbt_test_helper):
|
|
7
|
-
csv_data_curr = """
|
|
8
|
-
customer_id,name,age
|
|
9
|
-
1,Alice,30
|
|
10
|
-
2,Bob,25
|
|
11
|
-
3,Charlie,35
|
|
12
|
-
"""
|
|
13
|
-
|
|
14
|
-
csv_data_base = """
|
|
15
|
-
customer_id,name,age
|
|
16
|
-
1,Alice,35
|
|
17
|
-
2,Bob,25
|
|
18
|
-
3,Charlie,35
|
|
19
|
-
"""
|
|
20
|
-
|
|
21
|
-
dbt_test_helper.create_model("customers", csv_data_base, csv_data_curr)
|
|
22
|
-
params = dict(model='customers', primary_key=['customer_id'])
|
|
23
|
-
task = ValueDiffTask(params)
|
|
24
|
-
run_result = task.execute()
|
|
25
|
-
assert len(run_result.data.columns) == 3
|
|
26
|
-
assert len(run_result.data.data) == 3
|
|
27
|
-
|
|
28
|
-
params = dict(model='customers', primary_key=['customer_id'])
|
|
29
|
-
task = ValueDiffDetailTask(params)
|
|
30
|
-
run_result = task.execute()
|
|
31
|
-
assert len(run_result.columns) == 5
|
|
32
|
-
assert len(run_result.data) == 2
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
def test_validator():
|
|
36
|
-
from recce.tasks.valuediff import ValueDiffCheckValidator
|
|
37
|
-
|
|
38
|
-
def validate(params: dict = {}, view_options: dict = {}):
|
|
39
|
-
ValueDiffCheckValidator().validate({
|
|
40
|
-
'name': 'test',
|
|
41
|
-
'type': 'value_diff',
|
|
42
|
-
'params': params,
|
|
43
|
-
'view_options': view_options,
|
|
44
|
-
})
|
|
45
|
-
|
|
46
|
-
validate({
|
|
47
|
-
'model': 'customers',
|
|
48
|
-
'primary_key': 'customer_id',
|
|
49
|
-
})
|
|
50
|
-
validate({
|
|
51
|
-
'model': 'customers',
|
|
52
|
-
'primary_key': ['customer_id'],
|
|
53
|
-
})
|
|
54
|
-
validate({
|
|
55
|
-
'model': 'customers',
|
|
56
|
-
'primary_key': ['customer_id'],
|
|
57
|
-
'columns': ['name', 'age'],
|
|
58
|
-
})
|
|
59
|
-
|
|
60
|
-
with pytest.raises(ValueError):
|
|
61
|
-
validate({
|
|
62
|
-
})
|
|
63
|
-
|
|
64
|
-
with pytest.raises(ValueError):
|
|
65
|
-
validate({
|
|
66
|
-
'model': 'customers',
|
|
67
|
-
})
|
|
68
|
-
|
|
69
|
-
with pytest.raises(ValueError):
|
|
70
|
-
validate({
|
|
71
|
-
'model': 'customers',
|
|
72
|
-
'primary_key': ['customer_id'],
|
|
73
|
-
'columns': 'name',
|
|
74
|
-
})
|
tests/test_cli.py
DELETED
|
@@ -1,122 +0,0 @@
|
|
|
1
|
-
from unittest import TestCase
|
|
2
|
-
from unittest.mock import patch, MagicMock
|
|
3
|
-
|
|
4
|
-
from click.testing import CliRunner
|
|
5
|
-
|
|
6
|
-
from recce.cli import server as cli_command_server, run as cli_command_run
|
|
7
|
-
from recce.core import RecceContext
|
|
8
|
-
from recce.state import RecceStateLoader
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
def test_cmd_version():
|
|
12
|
-
from recce.cli import version
|
|
13
|
-
from recce import __version__
|
|
14
|
-
runner = CliRunner()
|
|
15
|
-
result = runner.invoke(version, [])
|
|
16
|
-
assert result.exit_code == 0
|
|
17
|
-
assert result.output.replace('\n', '') == __version__
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
class TestCommandServer(TestCase):
|
|
21
|
-
def setUp(self):
|
|
22
|
-
self.runner = CliRunner()
|
|
23
|
-
pass
|
|
24
|
-
|
|
25
|
-
@patch.object(RecceContext, 'verify_required_artifacts')
|
|
26
|
-
@patch('recce.cli.uvicorn.run')
|
|
27
|
-
def test_cmd_server(self, mock_run, mock_verify_required_artifacts):
|
|
28
|
-
from recce.server import app
|
|
29
|
-
mock_verify_required_artifacts.return_value = True, None
|
|
30
|
-
self.runner.invoke(cli_command_server, ['--host', 'unittest', '--port', 5566])
|
|
31
|
-
mock_run.assert_called_once_with(app, host='unittest', port=5566, lifespan='on')
|
|
32
|
-
|
|
33
|
-
@patch('recce.cli.uvicorn.run')
|
|
34
|
-
def test_cmd_server_with_cloud_without_password(self, mock_run):
|
|
35
|
-
# Should fail if no password is provided
|
|
36
|
-
result = self.runner.invoke(cli_command_server, ['--cloud'])
|
|
37
|
-
assert result.exit_code == 1
|
|
38
|
-
|
|
39
|
-
@patch('recce.cli.uvicorn.run')
|
|
40
|
-
def test_cmd_server_with_cloud_without_token(self, mock_run):
|
|
41
|
-
# Should fail if no token is provided
|
|
42
|
-
result = self.runner.invoke(cli_command_server, ['--cloud', '--password', 'unittest'])
|
|
43
|
-
assert result.exit_code == 1
|
|
44
|
-
|
|
45
|
-
@patch.object(RecceContext, 'verify_required_artifacts')
|
|
46
|
-
@patch('recce.util.recce_cloud.get_recce_cloud_onboarding_state')
|
|
47
|
-
@patch('recce.cli.uvicorn.run')
|
|
48
|
-
@patch('recce.cli.RecceStateLoader')
|
|
49
|
-
def test_cmd_server_with_cloud(self, mock_state_loader_class, mock_run, mock_get_recce_cloud_onboarding_state,
|
|
50
|
-
mock_verify_required_artifacts):
|
|
51
|
-
mock_state_loader = MagicMock(spec=RecceStateLoader)
|
|
52
|
-
mock_state_loader.verify.return_value = True
|
|
53
|
-
mock_state_loader.review_mode = True
|
|
54
|
-
mock_get_recce_cloud_onboarding_state.return_value = 'completed'
|
|
55
|
-
mock_verify_required_artifacts.return_value = True, None
|
|
56
|
-
|
|
57
|
-
mock_state_loader_class.return_value = mock_state_loader
|
|
58
|
-
self.runner.invoke(cli_command_server, ['--cloud', '--password', 'unittest', '--cloud-token', 'unittest'])
|
|
59
|
-
mock_state_loader_class.assert_called_once()
|
|
60
|
-
mock_run.assert_called_once()
|
|
61
|
-
|
|
62
|
-
@patch.object(RecceContext, 'verify_required_artifacts')
|
|
63
|
-
@patch('os.path.isdir', side_effect=lambda path: True if path == 'existed_folder' else False)
|
|
64
|
-
@patch('recce.cli.uvicorn.run')
|
|
65
|
-
@patch('recce.server.AppState')
|
|
66
|
-
def test_cmd_server_with_single_env(self,
|
|
67
|
-
mock_app_state, mock_run, mock_isdir, mock_verify_required_artifacts):
|
|
68
|
-
mock_verify_required_artifacts.return_value = True, None
|
|
69
|
-
self.runner.invoke(cli_command_server,
|
|
70
|
-
[
|
|
71
|
-
'--target-path', 'existed_folder',
|
|
72
|
-
'--target-base-path', 'non_existed_folder',
|
|
73
|
-
])
|
|
74
|
-
mock_run.assert_called_once()
|
|
75
|
-
|
|
76
|
-
# Onboarding mode should be set to True
|
|
77
|
-
app_state_call_args = mock_app_state.call_args
|
|
78
|
-
app_state_flag = app_state_call_args.kwargs['flag']
|
|
79
|
-
assert 'single_env_onboarding' in app_state_flag
|
|
80
|
-
assert app_state_flag['single_env_onboarding'] is True
|
|
81
|
-
assert 'show_relaunch_hint' in app_state_flag
|
|
82
|
-
assert app_state_flag['show_relaunch_hint'] is True
|
|
83
|
-
|
|
84
|
-
# The target_base_path should be set to the same as target_path
|
|
85
|
-
verify_required_artifacts_args = mock_verify_required_artifacts.call_args
|
|
86
|
-
assert verify_required_artifacts_args.kwargs['target_path'] == verify_required_artifacts_args.kwargs[
|
|
87
|
-
'target_base_path']
|
|
88
|
-
|
|
89
|
-
@patch.object(RecceContext, 'verify_required_artifacts')
|
|
90
|
-
@patch('os.path.isdir', side_effect=lambda path: True if path == 'existed_folder' else False)
|
|
91
|
-
@patch('recce.cli.uvicorn.run')
|
|
92
|
-
@patch('recce.server.AppState')
|
|
93
|
-
def test_cmd_server_with_single_env_but_review_mode_enabled(self,
|
|
94
|
-
mock_app_state, mock_run, mock_isdir,
|
|
95
|
-
mock_verify_required_artifacts):
|
|
96
|
-
mock_verify_required_artifacts.return_value = True, None
|
|
97
|
-
self.runner.invoke(cli_command_server,
|
|
98
|
-
[
|
|
99
|
-
'existed_state_file',
|
|
100
|
-
'--review',
|
|
101
|
-
'--target-path', 'existed_folder',
|
|
102
|
-
'--target-base-path', 'non_existed_folder',
|
|
103
|
-
])
|
|
104
|
-
mock_run.assert_called_once()
|
|
105
|
-
app_state_call_args = mock_app_state.call_args
|
|
106
|
-
app_state_flag = app_state_call_args.kwargs['flag']
|
|
107
|
-
assert 'single_env_onboarding' in app_state_flag
|
|
108
|
-
assert app_state_flag['single_env_onboarding'] is False
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
class TestCommandRun(TestCase):
|
|
112
|
-
def setUp(self):
|
|
113
|
-
self.runner = CliRunner()
|
|
114
|
-
pass
|
|
115
|
-
|
|
116
|
-
@patch.object(RecceContext, 'verify_required_artifacts')
|
|
117
|
-
@patch('recce.cli.cli_run')
|
|
118
|
-
def test_cmd_run(self, mock_cli_run, mock_verify_required_artifacts):
|
|
119
|
-
mock_verify_required_artifacts.return_value = True, None
|
|
120
|
-
|
|
121
|
-
self.runner.invoke(cli_command_run, [])
|
|
122
|
-
mock_cli_run.assert_called_once()
|
tests/test_config.py
DELETED
|
@@ -1,45 +0,0 @@
|
|
|
1
|
-
import os.path
|
|
2
|
-
from unittest import TestCase
|
|
3
|
-
from unittest.mock import patch
|
|
4
|
-
|
|
5
|
-
from recce.config import RecceConfig
|
|
6
|
-
from recce.util import SingletonMeta
|
|
7
|
-
|
|
8
|
-
test_root_path = os.path.dirname(os.path.abspath(__file__))
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
class RecceConfigTestCase(TestCase):
|
|
12
|
-
def setUp(self):
|
|
13
|
-
self.recce_config_path = os.path.join(test_root_path, 'data', 'config', 'recce.yml')
|
|
14
|
-
pass
|
|
15
|
-
|
|
16
|
-
def tearDown(self):
|
|
17
|
-
# Reset the SingletonMeta instances due to RecceConfig is a singleton
|
|
18
|
-
SingletonMeta._instances = {}
|
|
19
|
-
|
|
20
|
-
def test_load_recce_config(self):
|
|
21
|
-
config = RecceConfig(self.recce_config_path)
|
|
22
|
-
|
|
23
|
-
# Test data contains 2 checks
|
|
24
|
-
preset_checks = config.config.get('checks')
|
|
25
|
-
self.assertIsNotNone(preset_checks)
|
|
26
|
-
self.assertIsInstance(preset_checks, list)
|
|
27
|
-
self.assertEqual(len(preset_checks), 2)
|
|
28
|
-
|
|
29
|
-
@patch('recce.config.RecceConfig.save')
|
|
30
|
-
def test_recce_config_not_found(self, mock_save):
|
|
31
|
-
default_config = RecceConfig('NOT_EXISTING_FILE')
|
|
32
|
-
assert mock_save.called is True
|
|
33
|
-
# Default config should be generated
|
|
34
|
-
preset_checks = default_config.config.get('checks')
|
|
35
|
-
self.assertIsNotNone(default_config.config)
|
|
36
|
-
self.assertIsInstance(preset_checks, list)
|
|
37
|
-
self.assertEqual(len(preset_checks), 2)
|
|
38
|
-
|
|
39
|
-
@patch('recce.yaml.safe_load')
|
|
40
|
-
def test_recce_config_null_checks(self, mock_yaml_safe_load):
|
|
41
|
-
# mock to load a yaml file with null checks
|
|
42
|
-
mock_yaml_safe_load.return_value = {
|
|
43
|
-
'checks': None
|
|
44
|
-
}
|
|
45
|
-
RecceConfig(self.recce_config_path)
|
tests/test_core.py
DELETED
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
# noinspection PyUnresolvedReferences
|
|
2
|
-
from tests.adapter.dbt_adapter.conftest import dbt_test_helper
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
def test_lineage_diff(dbt_test_helper):
|
|
6
|
-
sql_model1 = """
|
|
7
|
-
select a from T
|
|
8
|
-
"""
|
|
9
|
-
|
|
10
|
-
sql_model2 = """
|
|
11
|
-
select a from {{ ref("model1") }}
|
|
12
|
-
"""
|
|
13
|
-
|
|
14
|
-
sql_model2_ = """
|
|
15
|
-
select
|
|
16
|
-
a,b
|
|
17
|
-
from
|
|
18
|
-
{{ ref("model1") }}
|
|
19
|
-
"""
|
|
20
|
-
|
|
21
|
-
dbt_test_helper.create_model("model1", sql_model1, sql_model1)
|
|
22
|
-
dbt_test_helper.create_model("model2", sql_model2, sql_model2_)
|
|
23
|
-
result = dbt_test_helper.context.get_lineage_diff()
|
|
24
|
-
nodediff = result.diff.get('model1')
|
|
25
|
-
assert nodediff is None
|
|
26
|
-
nodediff2 = result.diff.get('model2')
|
|
27
|
-
assert nodediff2 is not None and nodediff2.change_status == 'modified' and nodediff2.change.category == 'non_breaking'
|
tests/test_dbt.py
DELETED
|
@@ -1,36 +0,0 @@
|
|
|
1
|
-
import os
|
|
2
|
-
from unittest import TestCase
|
|
3
|
-
from unittest.mock import patch, MagicMock
|
|
4
|
-
|
|
5
|
-
from recce.adapter.dbt_adapter import load_manifest, load_catalog, DbtAdapter
|
|
6
|
-
|
|
7
|
-
current_dir = os.path.dirname(os.path.abspath(__file__))
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
class TestAdapterLineage(TestCase):
|
|
11
|
-
def setUp(self) -> None:
|
|
12
|
-
self.manifest = load_manifest(path=os.path.join(current_dir, 'manifest.json'))
|
|
13
|
-
assert self.manifest is not None
|
|
14
|
-
|
|
15
|
-
self.catalog = load_catalog(path=os.path.join(current_dir, 'catalog.json'))
|
|
16
|
-
assert self.catalog is not None
|
|
17
|
-
|
|
18
|
-
def tearDown(self):
|
|
19
|
-
pass
|
|
20
|
-
|
|
21
|
-
def test_load_lineage(self):
|
|
22
|
-
dbt_adapter = DbtAdapter(curr_manifest=self.manifest)
|
|
23
|
-
lineage = dbt_adapter.get_lineage()
|
|
24
|
-
assert lineage is not None
|
|
25
|
-
assert lineage['nodes']['model.jaffle_shop.orders'] is not None
|
|
26
|
-
assert 'columns' not in lineage['nodes']['model.jaffle_shop.orders']
|
|
27
|
-
|
|
28
|
-
def test_load_lineage_with_catalog(self):
|
|
29
|
-
mock_adapter = MagicMock()
|
|
30
|
-
mock_adapter.type.return_value = None
|
|
31
|
-
|
|
32
|
-
dbt_adapter = DbtAdapter(curr_manifest=self.manifest, curr_catalog=self.catalog)
|
|
33
|
-
dbt_adapter.adapter = mock_adapter
|
|
34
|
-
lineage = dbt_adapter.get_lineage()
|
|
35
|
-
assert lineage is not None
|
|
36
|
-
assert len(lineage['nodes']['model.jaffle_shop.orders']['columns']) == 9
|
tests/test_pull_request.py
DELETED
|
@@ -1,130 +0,0 @@
|
|
|
1
|
-
import json
|
|
2
|
-
from unittest.mock import patch
|
|
3
|
-
|
|
4
|
-
import pytest
|
|
5
|
-
import requests
|
|
6
|
-
|
|
7
|
-
from recce.pull_request import fetch_pr_metadata_from_event_path, _fetch_pr_title
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
@pytest.fixture
|
|
11
|
-
def mock_github_event(tmp_path, monkeypatch):
|
|
12
|
-
event_data = {
|
|
13
|
-
"number": 1,
|
|
14
|
-
"pull_request": {
|
|
15
|
-
"_links": {
|
|
16
|
-
"html": {"href": "https://github.com/xyz/abc/pull/1"},
|
|
17
|
-
"self": {"href": "api_url_here"},
|
|
18
|
-
}
|
|
19
|
-
},
|
|
20
|
-
}
|
|
21
|
-
event_file = tmp_path / "github_event.json"
|
|
22
|
-
with open(event_file, "w") as f:
|
|
23
|
-
json.dump(event_data, f)
|
|
24
|
-
|
|
25
|
-
monkeypatch.setenv("GITHUB_EVENT_PATH", str(event_file))
|
|
26
|
-
monkeypatch.setenv("GITHUB_REPOSITORY", "abc/xyz")
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
@pytest.fixture
|
|
30
|
-
def mock_github_token(monkeypatch):
|
|
31
|
-
monkeypatch.setenv("GITHUB_TOKEN", "github_token_here")
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
class MockResponse:
|
|
35
|
-
def __init__(self, status_code, json_data=None):
|
|
36
|
-
self.status_code = status_code
|
|
37
|
-
self.json_data = json_data
|
|
38
|
-
|
|
39
|
-
def json(self):
|
|
40
|
-
return self.json_data
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
@pytest.fixture
|
|
44
|
-
def mock_get_request_success(monkeypatch):
|
|
45
|
-
def mock_get(*args, **kwargs):
|
|
46
|
-
return MockResponse(200, json_data={"title": "Update README.md"})
|
|
47
|
-
|
|
48
|
-
monkeypatch.setattr(requests, "get", mock_get)
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
@pytest.fixture
|
|
52
|
-
def mock_get_request_failure(monkeypatch):
|
|
53
|
-
def mock_get(*args, **kwargs):
|
|
54
|
-
return MockResponse(404)
|
|
55
|
-
|
|
56
|
-
monkeypatch.setattr(requests, "get", mock_get)
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
def test_fetch_pr_metadata(mock_github_event, mock_github_token, mock_get_request_success):
|
|
60
|
-
result = fetch_pr_metadata_from_event_path()
|
|
61
|
-
|
|
62
|
-
assert result is not None
|
|
63
|
-
assert result["github_pr_id"] == 1
|
|
64
|
-
assert result["github_pr_url"] == "https://github.com/xyz/abc/pull/1"
|
|
65
|
-
assert result["github_pr_title"] == "Update README.md"
|
|
66
|
-
assert result["github_repository"] == "abc/xyz"
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
def test_fetch_pr_metadata_no_event(mock_get_request_success, monkeypatch):
|
|
70
|
-
monkeypatch.delenv("GITHUB_EVENT_PATH", raising=False)
|
|
71
|
-
|
|
72
|
-
result = fetch_pr_metadata_from_event_path()
|
|
73
|
-
|
|
74
|
-
assert result is None
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
def test_fetch_pr_metadata_request_error(mock_github_event, mock_get_request_failure):
|
|
78
|
-
result = fetch_pr_metadata_from_event_path()
|
|
79
|
-
|
|
80
|
-
assert result is not None
|
|
81
|
-
assert result["github_pr_id"] == 1
|
|
82
|
-
assert result["github_pr_url"] == "https://github.com/xyz/abc/pull/1"
|
|
83
|
-
assert result["github_pr_title"] is None
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
def test_fetch_pr_metadata_exception(mock_github_event, monkeypatch):
|
|
87
|
-
monkeypatch.setenv("GITHUB_EVENT_PATH", "nonexistent_path")
|
|
88
|
-
|
|
89
|
-
result = fetch_pr_metadata_from_event_path()
|
|
90
|
-
|
|
91
|
-
assert result is None
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
def test_fetch_pr_metadata_no_token(mock_github_event):
|
|
95
|
-
with patch.dict("os.environ", clear=True):
|
|
96
|
-
result = fetch_pr_metadata_from_event_path()
|
|
97
|
-
|
|
98
|
-
assert result is None
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
def test_fetch_pr_title(mock_github_token, mock_get_request_success):
|
|
102
|
-
result = _fetch_pr_title("api_url_here")
|
|
103
|
-
|
|
104
|
-
assert result == "Update README.md"
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
def test_fetch_pr_title_request_error(mock_github_token, mock_get_request_failure):
|
|
108
|
-
result = _fetch_pr_title("api_url_here")
|
|
109
|
-
|
|
110
|
-
assert result is None
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
def test_fetch_pr_title_exception(mock_github_token, monkeypatch):
|
|
114
|
-
monkeypatch.setenv("GITHUB_TOKEN", "github_token_here")
|
|
115
|
-
|
|
116
|
-
def mock_get(*args, **kwargs):
|
|
117
|
-
raise Exception("Test exception")
|
|
118
|
-
|
|
119
|
-
monkeypatch.setattr(requests, "get", mock_get)
|
|
120
|
-
|
|
121
|
-
result = _fetch_pr_title("api_url_here")
|
|
122
|
-
|
|
123
|
-
assert result is None
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
def test_fetch_pr_title_no_token():
|
|
127
|
-
with patch.dict("os.environ", clear=True):
|
|
128
|
-
result = _fetch_pr_title("api_url_here")
|
|
129
|
-
|
|
130
|
-
assert result is None
|
tests/test_server.py
DELETED
|
@@ -1,98 +0,0 @@
|
|
|
1
|
-
import os
|
|
2
|
-
|
|
3
|
-
import pytest
|
|
4
|
-
from fastapi.testclient import TestClient
|
|
5
|
-
|
|
6
|
-
from recce.core import default_context
|
|
7
|
-
from recce.server import app
|
|
8
|
-
# noinspection PyUnresolvedReferences
|
|
9
|
-
from tests.adapter.dbt_adapter.conftest import dbt_test_helper
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
@pytest.fixture
|
|
13
|
-
def temp_folder():
|
|
14
|
-
import tempfile
|
|
15
|
-
temp_dir = tempfile.mkdtemp()
|
|
16
|
-
yield temp_dir
|
|
17
|
-
|
|
18
|
-
import shutil
|
|
19
|
-
shutil.rmtree(temp_dir)
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
def test_health():
|
|
23
|
-
client = TestClient(app)
|
|
24
|
-
response = client.get("/api/health")
|
|
25
|
-
assert response.status_code == 200
|
|
26
|
-
assert response.json() == {"status": "ok"}
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
def test_stateless(dbt_test_helper):
|
|
30
|
-
context = default_context()
|
|
31
|
-
from recce.state import RecceStateLoader
|
|
32
|
-
context.state_loader = RecceStateLoader()
|
|
33
|
-
client = TestClient(app)
|
|
34
|
-
response = client.get("/api/info")
|
|
35
|
-
assert response.status_code == 200
|
|
36
|
-
info = response.json()
|
|
37
|
-
assert info['file_mode'] is False
|
|
38
|
-
assert info['cloud_mode'] is False
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
def test_file_mode(dbt_test_helper):
|
|
42
|
-
context = default_context()
|
|
43
|
-
from recce.state import RecceStateLoader
|
|
44
|
-
context.state_loader = RecceStateLoader(state_file='/tmp/recce_state.json')
|
|
45
|
-
client = TestClient(app)
|
|
46
|
-
response = client.get("/api/info")
|
|
47
|
-
assert response.status_code == 200
|
|
48
|
-
info = response.json()
|
|
49
|
-
assert info['file_mode'] is True
|
|
50
|
-
assert info['filename'] == 'recce_state.json'
|
|
51
|
-
assert info['cloud_mode'] is False
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
def test_saveas_and_rename(dbt_test_helper, temp_folder):
|
|
55
|
-
context = default_context()
|
|
56
|
-
state_file = os.path.join(temp_folder, 'recce_state.json')
|
|
57
|
-
state_file2 = os.path.join(temp_folder, 'recce_state2.json')
|
|
58
|
-
state_file3 = os.path.join(temp_folder, 'recce_state3.json')
|
|
59
|
-
os.makedirs(os.path.join(temp_folder, 'dir.json'))
|
|
60
|
-
|
|
61
|
-
from recce.state import RecceStateLoader
|
|
62
|
-
context.state_loader = RecceStateLoader(state_file=state_file)
|
|
63
|
-
client = TestClient(app)
|
|
64
|
-
|
|
65
|
-
response = client.post("/api/save", json={"filename": "recce_state2.json"})
|
|
66
|
-
assert response.status_code == 200
|
|
67
|
-
assert os.path.exists(state_file)
|
|
68
|
-
|
|
69
|
-
response = client.post("/api/save-as", json={"filename": "recce_state2.json"})
|
|
70
|
-
assert response.status_code == 200
|
|
71
|
-
assert os.path.exists(state_file2)
|
|
72
|
-
assert context.state_loader.state_file == os.path.join(temp_folder, 'recce_state2.json')
|
|
73
|
-
|
|
74
|
-
# Same file
|
|
75
|
-
response = client.post("/api/save-as", json={"filename": "recce_state2.json"})
|
|
76
|
-
assert response.status_code == 400
|
|
77
|
-
|
|
78
|
-
# folder
|
|
79
|
-
response = client.post("/api/save-as", json={"filename": "dir.json"})
|
|
80
|
-
assert response.status_code == 400
|
|
81
|
-
|
|
82
|
-
# Rename
|
|
83
|
-
response = client.post("/api/rename", json={"filename": "recce_state3.json"})
|
|
84
|
-
assert response.status_code == 200
|
|
85
|
-
assert not os.path.exists(state_file2)
|
|
86
|
-
assert os.path.exists(state_file3)
|
|
87
|
-
assert context.state_loader.state_file == os.path.join(temp_folder, 'recce_state3.json')
|
|
88
|
-
|
|
89
|
-
# Conflict
|
|
90
|
-
response = client.post("/api/save-as", json={"filename": "recce_state.json"})
|
|
91
|
-
assert response.status_code == 409
|
|
92
|
-
response = client.post("/api/rename", json={"filename": "recce_state.json"})
|
|
93
|
-
assert response.status_code == 409
|
|
94
|
-
|
|
95
|
-
# Overwrite
|
|
96
|
-
response = client.post("/api/save-as", json={"filename": "recce_state.json", "overwrite": True})
|
|
97
|
-
assert response.status_code == 200
|
|
98
|
-
assert context.state_loader.state_file == os.path.join(temp_folder, 'recce_state.json')
|