recce-nightly 1.3.0.20250507__py3-none-any.whl → 1.4.0.20250514__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 +22 -22
- recce/adapter/base.py +11 -14
- recce/adapter/dbt_adapter/__init__.py +355 -316
- recce/adapter/dbt_adapter/dbt_version.py +3 -0
- recce/adapter/sqlmesh_adapter.py +24 -35
- recce/apis/check_api.py +39 -28
- recce/apis/check_func.py +33 -27
- recce/apis/run_api.py +25 -19
- recce/apis/run_func.py +29 -23
- recce/artifact.py +44 -49
- recce/cli.py +484 -285
- recce/config.py +42 -33
- recce/core.py +52 -44
- recce/data/404.html +1 -1
- recce/data/_next/static/chunks/{368-7587b306577df275.js → 778-aef312bffb4c0312.js} +15 -15
- recce/data/_next/static/chunks/8d700b6a.ed11a130057c7a47.js +1 -0
- recce/data/_next/static/chunks/app/layout-c713a2829d3279e4.js +1 -0
- recce/data/_next/static/chunks/app/page-7086764277331fcb.js +1 -0
- recce/data/_next/static/chunks/{cd9f8d63-cf0d5a7b0f7a92e8.js → cd9f8d63-e020f408095ed77c.js} +3 -3
- recce/data/_next/static/chunks/webpack-b787cb1a4f2293de.js +1 -0
- recce/data/_next/static/css/88b8abc134cfd59a.css +3 -0
- recce/data/index.html +2 -2
- recce/data/index.txt +2 -2
- recce/diff.py +6 -12
- recce/event/__init__.py +74 -72
- recce/event/collector.py +27 -20
- recce/event/track.py +39 -27
- recce/exceptions.py +1 -1
- recce/git.py +7 -7
- recce/github.py +57 -53
- recce/models/__init__.py +1 -1
- recce/models/check.py +6 -7
- recce/models/run.py +1 -0
- recce/models/types.py +27 -27
- recce/pull_request.py +26 -24
- recce/run.py +148 -111
- recce/server.py +103 -89
- recce/state.py +209 -177
- recce/summary.py +168 -143
- recce/tasks/__init__.py +3 -3
- recce/tasks/core.py +11 -13
- recce/tasks/dataframe.py +19 -17
- recce/tasks/histogram.py +69 -34
- recce/tasks/lineage.py +2 -2
- recce/tasks/profile.py +147 -86
- recce/tasks/query.py +139 -87
- recce/tasks/rowcount.py +33 -30
- recce/tasks/schema.py +14 -14
- recce/tasks/top_k.py +35 -35
- recce/tasks/valuediff.py +216 -152
- recce/util/breaking.py +77 -84
- recce/util/cll.py +55 -51
- recce/util/io.py +19 -17
- recce/util/logger.py +1 -1
- recce/util/recce_cloud.py +70 -72
- recce/util/singleton.py +4 -4
- recce/yaml/__init__.py +7 -10
- {recce_nightly-1.3.0.20250507.dist-info → recce_nightly-1.4.0.20250514.dist-info}/METADATA +5 -2
- recce_nightly-1.4.0.20250514.dist-info/RECORD +143 -0
- {recce_nightly-1.3.0.20250507.dist-info → recce_nightly-1.4.0.20250514.dist-info}/WHEEL +1 -1
- tests/adapter/dbt_adapter/conftest.py +1 -0
- tests/adapter/dbt_adapter/dbt_test_helper.py +28 -18
- tests/adapter/dbt_adapter/test_dbt_adapter.py +0 -15
- tests/adapter/dbt_adapter/test_dbt_cll.py +39 -32
- tests/adapter/dbt_adapter/test_selector.py +22 -21
- tests/tasks/test_histogram.py +58 -66
- tests/tasks/test_lineage.py +36 -23
- tests/tasks/test_preset_checks.py +45 -31
- tests/tasks/test_profile.py +340 -15
- tests/tasks/test_query.py +40 -40
- tests/tasks/test_row_count.py +65 -46
- tests/tasks/test_schema.py +65 -42
- tests/tasks/test_top_k.py +22 -18
- tests/tasks/test_valuediff.py +43 -32
- tests/test_cli.py +71 -58
- tests/test_config.py +7 -9
- tests/test_core.py +5 -3
- tests/test_dbt.py +7 -7
- tests/test_pull_request.py +1 -1
- tests/test_server.py +19 -13
- tests/test_state.py +40 -27
- tests/test_summary.py +18 -14
- recce/data/_next/static/chunks/8d700b6a-f0b1f6b9e0d97ce2.js +0 -1
- recce/data/_next/static/chunks/app/layout-9102e22cb73f74d6.js +0 -1
- recce/data/_next/static/chunks/app/page-92f13c8fad9fae3d.js +0 -1
- recce/data/_next/static/chunks/webpack-567d72f0bc0820d5.js +0 -1
- recce_nightly-1.3.0.20250507.dist-info/RECORD +0 -142
- /recce/data/_next/static/{K5iKlCYhdcpq8Ea6ck9J_ → E_HPXsXdrqHg2YEHmU3mK}/_buildManifest.js +0 -0
- /recce/data/_next/static/{K5iKlCYhdcpq8Ea6ck9J_ → E_HPXsXdrqHg2YEHmU3mK}/_ssgManifest.js +0 -0
- {recce_nightly-1.3.0.20250507.dist-info → recce_nightly-1.4.0.20250514.dist-info}/entry_points.txt +0 -0
- {recce_nightly-1.3.0.20250507.dist-info → recce_nightly-1.4.0.20250514.dist-info}/licenses/LICENSE +0 -0
- {recce_nightly-1.3.0.20250507.dist-info → recce_nightly-1.4.0.20250514.dist-info}/top_level.txt +0 -0
tests/tasks/test_row_count.py
CHANGED
|
@@ -17,21 +17,21 @@ def test_row_count(dbt_test_helper):
|
|
|
17
17
|
2,Bob,25
|
|
18
18
|
"""
|
|
19
19
|
|
|
20
|
-
dbt_test_helper.create_model("customers", csv_data_base, csv_data_curr, unique_id=
|
|
21
|
-
task = RowCountDiffTask(dict(node_names=[
|
|
20
|
+
dbt_test_helper.create_model("customers", csv_data_base, csv_data_curr, unique_id="model.customers")
|
|
21
|
+
task = RowCountDiffTask(dict(node_names=["customers"]))
|
|
22
22
|
run_result = task.execute()
|
|
23
|
-
assert run_result[
|
|
24
|
-
assert run_result[
|
|
23
|
+
assert run_result["customers"]["base"] == 2
|
|
24
|
+
assert run_result["customers"]["curr"] == 3
|
|
25
25
|
|
|
26
|
-
task = RowCountDiffTask(dict(node_names=[
|
|
26
|
+
task = RowCountDiffTask(dict(node_names=["customers_"]))
|
|
27
27
|
run_result = task.execute()
|
|
28
|
-
assert run_result[
|
|
29
|
-
assert run_result[
|
|
28
|
+
assert run_result["customers_"]["base"] is None
|
|
29
|
+
assert run_result["customers_"]["curr"] is None
|
|
30
30
|
|
|
31
|
-
task = RowCountDiffTask(dict(node_ids=[
|
|
31
|
+
task = RowCountDiffTask(dict(node_ids=["model.customers"]))
|
|
32
32
|
run_result = task.execute()
|
|
33
|
-
assert run_result[
|
|
34
|
-
assert run_result[
|
|
33
|
+
assert run_result["customers"]["base"] == 2
|
|
34
|
+
assert run_result["customers"]["curr"] == 3
|
|
35
35
|
|
|
36
36
|
|
|
37
37
|
def test_row_count_with_selector(dbt_test_helper):
|
|
@@ -49,13 +49,14 @@ def test_row_count_with_selector(dbt_test_helper):
|
|
|
49
49
|
"""
|
|
50
50
|
|
|
51
51
|
dbt_test_helper.create_model("model_1", csv_data_1, csv_data_2, depends_on=[])
|
|
52
|
-
dbt_test_helper.create_model(
|
|
53
|
-
|
|
54
|
-
|
|
52
|
+
dbt_test_helper.create_model(
|
|
53
|
+
"model_2", csv_data_1, csv_data_1, depends_on=["model_1"], package_name="other_package"
|
|
54
|
+
)
|
|
55
|
+
task = RowCountDiffTask(dict(select="model_1"))
|
|
55
56
|
run_result = task.execute()
|
|
56
57
|
assert len(run_result) == 1
|
|
57
58
|
|
|
58
|
-
task = RowCountDiffTask(dict(select=
|
|
59
|
+
task = RowCountDiffTask(dict(select="model_1+"))
|
|
59
60
|
run_result = task.execute()
|
|
60
61
|
assert len(run_result) == 2
|
|
61
62
|
|
|
@@ -66,51 +67,69 @@ def test_validator():
|
|
|
66
67
|
validator = RowCountDiffCheckValidator()
|
|
67
68
|
|
|
68
69
|
def validate(params: dict):
|
|
69
|
-
validator.validate(
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
70
|
+
validator.validate(
|
|
71
|
+
{
|
|
72
|
+
"name": "test",
|
|
73
|
+
"type": "row_count_diff",
|
|
74
|
+
"params": params,
|
|
75
|
+
}
|
|
76
|
+
)
|
|
74
77
|
|
|
75
78
|
# Select all modesl
|
|
76
79
|
validate({})
|
|
77
80
|
|
|
78
81
|
# Select by node name
|
|
79
|
-
validate(
|
|
80
|
-
|
|
81
|
-
|
|
82
|
+
validate(
|
|
83
|
+
{
|
|
84
|
+
"node_names": ["abc"],
|
|
85
|
+
}
|
|
86
|
+
)
|
|
82
87
|
with pytest.raises(ValueError):
|
|
83
|
-
validate(
|
|
84
|
-
|
|
85
|
-
|
|
88
|
+
validate(
|
|
89
|
+
{
|
|
90
|
+
"node_names": "abc",
|
|
91
|
+
}
|
|
92
|
+
)
|
|
86
93
|
|
|
87
94
|
# Select by node id
|
|
88
|
-
validate(
|
|
89
|
-
|
|
90
|
-
|
|
95
|
+
validate(
|
|
96
|
+
{
|
|
97
|
+
"node_ids": ["model.abc"],
|
|
98
|
+
}
|
|
99
|
+
)
|
|
91
100
|
|
|
92
101
|
# Select by selector
|
|
93
|
-
validate(
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
102
|
+
validate(
|
|
103
|
+
{
|
|
104
|
+
"select": "customers",
|
|
105
|
+
"exclude": "customers",
|
|
106
|
+
"packages": ["jaffle_shop"],
|
|
107
|
+
"view_mode": "all",
|
|
108
|
+
}
|
|
109
|
+
)
|
|
99
110
|
|
|
100
111
|
# packages should be an array
|
|
101
112
|
with pytest.raises(ValueError):
|
|
102
|
-
validate(
|
|
103
|
-
|
|
104
|
-
|
|
113
|
+
validate(
|
|
114
|
+
{
|
|
115
|
+
"packages": "jaffle_shop",
|
|
116
|
+
}
|
|
117
|
+
)
|
|
105
118
|
|
|
106
119
|
# view_mode should be 'all' or 'changed_models'
|
|
107
|
-
validate(
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
120
|
+
validate(
|
|
121
|
+
{
|
|
122
|
+
"view_mode": None,
|
|
123
|
+
}
|
|
124
|
+
)
|
|
125
|
+
validate(
|
|
126
|
+
{
|
|
127
|
+
"view_mode": "all",
|
|
128
|
+
}
|
|
129
|
+
)
|
|
113
130
|
with pytest.raises(ValueError):
|
|
114
|
-
validate(
|
|
115
|
-
|
|
116
|
-
|
|
131
|
+
validate(
|
|
132
|
+
{
|
|
133
|
+
"view_mode": "abc",
|
|
134
|
+
}
|
|
135
|
+
)
|
tests/tasks/test_schema.py
CHANGED
|
@@ -4,58 +4,75 @@ from unittest.mock import MagicMock
|
|
|
4
4
|
|
|
5
5
|
import pytest
|
|
6
6
|
|
|
7
|
-
from recce.adapter.dbt_adapter import
|
|
7
|
+
from recce.adapter.dbt_adapter import DbtAdapter, load_catalog, load_manifest
|
|
8
8
|
from recce.core import RecceContext, set_default_context
|
|
9
9
|
from recce.run import schema_diff_should_be_approved
|
|
10
10
|
|
|
11
11
|
|
|
12
12
|
def test_validator():
|
|
13
13
|
from recce.tasks.schema import SchemaDiffCheckValidator
|
|
14
|
+
|
|
14
15
|
validator = SchemaDiffCheckValidator()
|
|
15
16
|
|
|
16
17
|
def validate(params: dict):
|
|
17
|
-
validator.validate(
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
18
|
+
validator.validate(
|
|
19
|
+
{
|
|
20
|
+
"name": "test",
|
|
21
|
+
"type": "schema_diff",
|
|
22
|
+
"params": params,
|
|
23
|
+
}
|
|
24
|
+
)
|
|
22
25
|
|
|
23
26
|
# Select all models
|
|
24
27
|
validate({})
|
|
25
28
|
|
|
26
29
|
# Select by node name
|
|
27
|
-
validate(
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
30
|
+
validate(
|
|
31
|
+
{
|
|
32
|
+
"node_id": "abc",
|
|
33
|
+
}
|
|
34
|
+
)
|
|
35
|
+
validate(
|
|
36
|
+
{
|
|
37
|
+
"node_id": ["abc"],
|
|
38
|
+
}
|
|
39
|
+
)
|
|
33
40
|
|
|
34
41
|
# Select by selector
|
|
35
|
-
validate(
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
42
|
+
validate(
|
|
43
|
+
{
|
|
44
|
+
"select": "customers",
|
|
45
|
+
"exclude": "customers",
|
|
46
|
+
"packages": ["jaffle_shop"],
|
|
47
|
+
"view_mode": "all",
|
|
48
|
+
}
|
|
49
|
+
)
|
|
41
50
|
|
|
42
51
|
# packages should be an array
|
|
43
52
|
with pytest.raises(ValueError):
|
|
44
|
-
validate(
|
|
45
|
-
|
|
46
|
-
|
|
53
|
+
validate(
|
|
54
|
+
{
|
|
55
|
+
"packages": "jaffle_shop",
|
|
56
|
+
}
|
|
57
|
+
)
|
|
47
58
|
|
|
48
59
|
# view_mode should be 'all' or 'changed_models'
|
|
49
|
-
validate(
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
60
|
+
validate(
|
|
61
|
+
{
|
|
62
|
+
"view_mode": None,
|
|
63
|
+
}
|
|
64
|
+
)
|
|
65
|
+
validate(
|
|
66
|
+
{
|
|
67
|
+
"view_mode": "all",
|
|
68
|
+
}
|
|
69
|
+
)
|
|
55
70
|
with pytest.raises(ValueError):
|
|
56
|
-
validate(
|
|
57
|
-
|
|
58
|
-
|
|
71
|
+
validate(
|
|
72
|
+
{
|
|
73
|
+
"view_mode": "abc",
|
|
74
|
+
}
|
|
75
|
+
)
|
|
59
76
|
|
|
60
77
|
|
|
61
78
|
test_root_path = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
|
@@ -65,8 +82,8 @@ class TestSchemaDiffAutoApprove(TestCase):
|
|
|
65
82
|
|
|
66
83
|
def setUp(self):
|
|
67
84
|
self.default_context = MagicMock(spec=RecceContext)
|
|
68
|
-
manifest = load_manifest(path=os.path.join(test_root_path,
|
|
69
|
-
catalog = load_catalog(path=os.path.join(test_root_path,
|
|
85
|
+
manifest = load_manifest(path=os.path.join(test_root_path, "manifest.json"))
|
|
86
|
+
catalog = load_catalog(path=os.path.join(test_root_path, "catalog.json"))
|
|
70
87
|
dbt_adapter = DbtAdapter(curr_manifest=manifest, curr_catalog=catalog)
|
|
71
88
|
self.default_context.adapter = dbt_adapter
|
|
72
89
|
|
|
@@ -80,20 +97,26 @@ class TestSchemaDiffAutoApprove(TestCase):
|
|
|
80
97
|
|
|
81
98
|
def test_schema_diff_should_be_approved(self):
|
|
82
99
|
# Node_id is string
|
|
83
|
-
is_approved = schema_diff_should_be_approved(
|
|
84
|
-
|
|
85
|
-
|
|
100
|
+
is_approved = schema_diff_should_be_approved(
|
|
101
|
+
{
|
|
102
|
+
"node_id": "model.jaffle_shop.customers",
|
|
103
|
+
}
|
|
104
|
+
)
|
|
86
105
|
assert is_approved is True
|
|
87
106
|
|
|
88
107
|
# Node_id is list
|
|
89
|
-
is_approved = schema_diff_should_be_approved(
|
|
90
|
-
|
|
91
|
-
|
|
108
|
+
is_approved = schema_diff_should_be_approved(
|
|
109
|
+
{
|
|
110
|
+
"node_id": ["model.jaffle_shop.customers"],
|
|
111
|
+
}
|
|
112
|
+
)
|
|
92
113
|
assert is_approved is True
|
|
93
114
|
|
|
94
115
|
# Select all models
|
|
95
|
-
self.default_context.adapter.select_nodes.return_value = [
|
|
96
|
-
is_approved = schema_diff_should_be_approved(
|
|
97
|
-
|
|
98
|
-
|
|
116
|
+
self.default_context.adapter.select_nodes.return_value = ["model.jaffle_shop.customers"]
|
|
117
|
+
is_approved = schema_diff_should_be_approved(
|
|
118
|
+
{
|
|
119
|
+
"select": "customers",
|
|
120
|
+
}
|
|
121
|
+
)
|
|
99
122
|
assert is_approved is True
|
tests/tasks/test_top_k.py
CHANGED
|
@@ -38,10 +38,10 @@ def test_top_k(dbt_test_helper):
|
|
|
38
38
|
# 'valids': 4,
|
|
39
39
|
# 'total': 4
|
|
40
40
|
# }
|
|
41
|
-
assert run_result[
|
|
42
|
-
assert run_result[
|
|
43
|
-
assert run_result[
|
|
44
|
-
assert run_result[
|
|
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
45
|
|
|
46
46
|
# {
|
|
47
47
|
# 'values': ['Bob', 'Alice', 'Charlie'],
|
|
@@ -49,25 +49,29 @@ def test_top_k(dbt_test_helper):
|
|
|
49
49
|
# 'valids': 3,
|
|
50
50
|
# 'total': 4
|
|
51
51
|
# }
|
|
52
|
-
assert run_result[
|
|
53
|
-
assert run_result[
|
|
54
|
-
assert run_result[
|
|
52
|
+
assert run_result["base"]["counts"][0] == 1
|
|
53
|
+
assert run_result["base"]["valids"] == 3
|
|
54
|
+
assert run_result["base"]["total"] == 4
|
|
55
55
|
|
|
56
56
|
|
|
57
57
|
def test_validator():
|
|
58
58
|
def validate(params: dict = {}, view_options: dict = {}):
|
|
59
|
-
TopKDiffCheckValidator().validate(
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
59
|
+
TopKDiffCheckValidator().validate(
|
|
60
|
+
{
|
|
61
|
+
"name": "test",
|
|
62
|
+
"type": "top_k_diff",
|
|
63
|
+
"params": params,
|
|
64
|
+
"view_options": view_options,
|
|
65
|
+
}
|
|
66
|
+
)
|
|
65
67
|
|
|
66
|
-
validate(
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
68
|
+
validate(
|
|
69
|
+
{
|
|
70
|
+
"model": "customers",
|
|
71
|
+
"column_name": "name",
|
|
72
|
+
"k": 50,
|
|
73
|
+
}
|
|
74
|
+
)
|
|
71
75
|
|
|
72
76
|
with pytest.raises(ValueError):
|
|
73
77
|
validate({})
|
tests/tasks/test_valuediff.py
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import pytest
|
|
2
2
|
|
|
3
|
-
from recce.tasks import
|
|
3
|
+
from recce.tasks import ValueDiffDetailTask, ValueDiffTask
|
|
4
4
|
|
|
5
5
|
|
|
6
6
|
def test_value_diff(dbt_test_helper):
|
|
@@ -19,13 +19,13 @@ def test_value_diff(dbt_test_helper):
|
|
|
19
19
|
"""
|
|
20
20
|
|
|
21
21
|
dbt_test_helper.create_model("customers", csv_data_base, csv_data_curr)
|
|
22
|
-
params = dict(model=
|
|
22
|
+
params = dict(model="customers", primary_key=["customer_id"])
|
|
23
23
|
task = ValueDiffTask(params)
|
|
24
24
|
run_result = task.execute()
|
|
25
25
|
assert len(run_result.data.columns) == 3
|
|
26
26
|
assert len(run_result.data.data) == 3
|
|
27
27
|
|
|
28
|
-
params = dict(model=
|
|
28
|
+
params = dict(model="customers", primary_key=["customer_id"])
|
|
29
29
|
task = ValueDiffDetailTask(params)
|
|
30
30
|
run_result = task.execute()
|
|
31
31
|
assert len(run_result.columns) == 5
|
|
@@ -36,39 +36,50 @@ def test_validator():
|
|
|
36
36
|
from recce.tasks.valuediff import ValueDiffCheckValidator
|
|
37
37
|
|
|
38
38
|
def validate(params: dict = {}, view_options: dict = {}):
|
|
39
|
-
ValueDiffCheckValidator().validate(
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
39
|
+
ValueDiffCheckValidator().validate(
|
|
40
|
+
{
|
|
41
|
+
"name": "test",
|
|
42
|
+
"type": "value_diff",
|
|
43
|
+
"params": params,
|
|
44
|
+
"view_options": view_options,
|
|
45
|
+
}
|
|
46
|
+
)
|
|
45
47
|
|
|
46
|
-
validate(
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
48
|
+
validate(
|
|
49
|
+
{
|
|
50
|
+
"model": "customers",
|
|
51
|
+
"primary_key": "customer_id",
|
|
52
|
+
}
|
|
53
|
+
)
|
|
54
|
+
validate(
|
|
55
|
+
{
|
|
56
|
+
"model": "customers",
|
|
57
|
+
"primary_key": ["customer_id"],
|
|
58
|
+
}
|
|
59
|
+
)
|
|
60
|
+
validate(
|
|
61
|
+
{
|
|
62
|
+
"model": "customers",
|
|
63
|
+
"primary_key": ["customer_id"],
|
|
64
|
+
"columns": ["name", "age"],
|
|
65
|
+
}
|
|
66
|
+
)
|
|
59
67
|
|
|
60
68
|
with pytest.raises(ValueError):
|
|
61
|
-
validate({
|
|
62
|
-
})
|
|
69
|
+
validate({})
|
|
63
70
|
|
|
64
71
|
with pytest.raises(ValueError):
|
|
65
|
-
validate(
|
|
66
|
-
|
|
67
|
-
|
|
72
|
+
validate(
|
|
73
|
+
{
|
|
74
|
+
"model": "customers",
|
|
75
|
+
}
|
|
76
|
+
)
|
|
68
77
|
|
|
69
78
|
with pytest.raises(ValueError):
|
|
70
|
-
validate(
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
79
|
+
validate(
|
|
80
|
+
{
|
|
81
|
+
"model": "customers",
|
|
82
|
+
"primary_key": ["customer_id"],
|
|
83
|
+
"columns": "name",
|
|
84
|
+
}
|
|
85
|
+
)
|
tests/test_cli.py
CHANGED
|
@@ -1,20 +1,22 @@
|
|
|
1
1
|
from unittest import TestCase
|
|
2
|
-
from unittest.mock import
|
|
2
|
+
from unittest.mock import MagicMock, patch
|
|
3
3
|
|
|
4
4
|
from click.testing import CliRunner
|
|
5
5
|
|
|
6
|
-
from recce.cli import
|
|
6
|
+
from recce.cli import run as cli_command_run
|
|
7
|
+
from recce.cli import server as cli_command_server
|
|
7
8
|
from recce.core import RecceContext
|
|
8
9
|
from recce.state import RecceStateLoader
|
|
9
10
|
|
|
10
11
|
|
|
11
12
|
def test_cmd_version():
|
|
12
|
-
from recce.cli import version
|
|
13
13
|
from recce import __version__
|
|
14
|
+
from recce.cli import version
|
|
15
|
+
|
|
14
16
|
runner = CliRunner()
|
|
15
17
|
result = runner.invoke(version, [])
|
|
16
18
|
assert result.exit_code == 0
|
|
17
|
-
assert result.output.replace(
|
|
19
|
+
assert result.output.replace("\n", "") == __version__
|
|
18
20
|
|
|
19
21
|
|
|
20
22
|
class TestCommandServer(TestCase):
|
|
@@ -22,90 +24,101 @@ class TestCommandServer(TestCase):
|
|
|
22
24
|
self.runner = CliRunner()
|
|
23
25
|
pass
|
|
24
26
|
|
|
25
|
-
@patch.object(RecceContext,
|
|
26
|
-
@patch(
|
|
27
|
+
@patch.object(RecceContext, "verify_required_artifacts")
|
|
28
|
+
@patch("recce.cli.uvicorn.run")
|
|
27
29
|
def test_cmd_server(self, mock_run, mock_verify_required_artifacts):
|
|
28
30
|
from recce.server import app
|
|
31
|
+
|
|
29
32
|
mock_verify_required_artifacts.return_value = True, None
|
|
30
|
-
self.runner.invoke(cli_command_server, [
|
|
31
|
-
mock_run.assert_called_once_with(app, host=
|
|
33
|
+
self.runner.invoke(cli_command_server, ["--host", "unittest", "--port", 5566])
|
|
34
|
+
mock_run.assert_called_once_with(app, host="unittest", port=5566, lifespan="on")
|
|
32
35
|
|
|
33
|
-
@patch(
|
|
36
|
+
@patch("recce.cli.uvicorn.run")
|
|
34
37
|
def test_cmd_server_with_cloud_without_password(self, mock_run):
|
|
35
38
|
# Should fail if no password is provided
|
|
36
|
-
result = self.runner.invoke(cli_command_server, [
|
|
39
|
+
result = self.runner.invoke(cli_command_server, ["--cloud"])
|
|
37
40
|
assert result.exit_code == 1
|
|
38
41
|
|
|
39
|
-
@patch(
|
|
42
|
+
@patch("recce.cli.uvicorn.run")
|
|
40
43
|
def test_cmd_server_with_cloud_without_token(self, mock_run):
|
|
41
44
|
# Should fail if no token is provided
|
|
42
|
-
result = self.runner.invoke(cli_command_server, [
|
|
45
|
+
result = self.runner.invoke(cli_command_server, ["--cloud", "--password", "unittest"])
|
|
43
46
|
assert result.exit_code == 1
|
|
44
47
|
|
|
45
|
-
@patch.object(RecceContext,
|
|
46
|
-
@patch(
|
|
47
|
-
@patch(
|
|
48
|
-
@patch(
|
|
49
|
-
def test_cmd_server_with_cloud(
|
|
50
|
-
|
|
48
|
+
@patch.object(RecceContext, "verify_required_artifacts")
|
|
49
|
+
@patch("recce.util.recce_cloud.get_recce_cloud_onboarding_state")
|
|
50
|
+
@patch("recce.cli.uvicorn.run")
|
|
51
|
+
@patch("recce.cli.RecceStateLoader")
|
|
52
|
+
def test_cmd_server_with_cloud(
|
|
53
|
+
self, mock_state_loader_class, mock_run, mock_get_recce_cloud_onboarding_state, mock_verify_required_artifacts
|
|
54
|
+
):
|
|
51
55
|
mock_state_loader = MagicMock(spec=RecceStateLoader)
|
|
52
56
|
mock_state_loader.verify.return_value = True
|
|
53
57
|
mock_state_loader.review_mode = True
|
|
54
|
-
mock_get_recce_cloud_onboarding_state.return_value =
|
|
58
|
+
mock_get_recce_cloud_onboarding_state.return_value = "completed"
|
|
55
59
|
mock_verify_required_artifacts.return_value = True, None
|
|
56
60
|
|
|
57
61
|
mock_state_loader_class.return_value = mock_state_loader
|
|
58
|
-
self.runner.invoke(cli_command_server, [
|
|
62
|
+
self.runner.invoke(cli_command_server, ["--cloud", "--password", "unittest", "--cloud-token", "unittest"])
|
|
59
63
|
mock_state_loader_class.assert_called_once()
|
|
60
64
|
mock_run.assert_called_once()
|
|
61
65
|
|
|
62
|
-
@patch.object(RecceContext,
|
|
63
|
-
@patch(
|
|
64
|
-
@patch(
|
|
65
|
-
@patch(
|
|
66
|
-
def test_cmd_server_with_single_env(self,
|
|
67
|
-
mock_app_state, mock_run, mock_isdir, mock_verify_required_artifacts):
|
|
66
|
+
@patch.object(RecceContext, "verify_required_artifacts")
|
|
67
|
+
@patch("os.path.isdir", side_effect=lambda path: True if path == "existed_folder" else False)
|
|
68
|
+
@patch("recce.cli.uvicorn.run")
|
|
69
|
+
@patch("recce.server.AppState")
|
|
70
|
+
def test_cmd_server_with_single_env(self, mock_app_state, mock_run, mock_isdir, mock_verify_required_artifacts):
|
|
68
71
|
mock_verify_required_artifacts.return_value = True, None
|
|
69
|
-
self.runner.invoke(
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
72
|
+
self.runner.invoke(
|
|
73
|
+
cli_command_server,
|
|
74
|
+
[
|
|
75
|
+
"--target-path",
|
|
76
|
+
"existed_folder",
|
|
77
|
+
"--target-base-path",
|
|
78
|
+
"non_existed_folder",
|
|
79
|
+
],
|
|
80
|
+
)
|
|
74
81
|
mock_run.assert_called_once()
|
|
75
82
|
|
|
76
83
|
# Onboarding mode should be set to True
|
|
77
84
|
app_state_call_args = mock_app_state.call_args
|
|
78
|
-
app_state_flag = app_state_call_args.kwargs[
|
|
79
|
-
assert
|
|
80
|
-
assert app_state_flag[
|
|
81
|
-
assert
|
|
82
|
-
assert app_state_flag[
|
|
85
|
+
app_state_flag = app_state_call_args.kwargs["flag"]
|
|
86
|
+
assert "single_env_onboarding" in app_state_flag
|
|
87
|
+
assert app_state_flag["single_env_onboarding"] is True
|
|
88
|
+
assert "show_relaunch_hint" in app_state_flag
|
|
89
|
+
assert app_state_flag["show_relaunch_hint"] is True
|
|
83
90
|
|
|
84
91
|
# The target_base_path should be set to the same as target_path
|
|
85
92
|
verify_required_artifacts_args = mock_verify_required_artifacts.call_args
|
|
86
|
-
assert
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
@patch(
|
|
92
|
-
@patch(
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
93
|
+
assert (
|
|
94
|
+
verify_required_artifacts_args.kwargs["target_path"]
|
|
95
|
+
== verify_required_artifacts_args.kwargs["target_base_path"]
|
|
96
|
+
)
|
|
97
|
+
|
|
98
|
+
@patch.object(RecceContext, "verify_required_artifacts")
|
|
99
|
+
@patch("os.path.isdir", side_effect=lambda path: True if path == "existed_folder" else False)
|
|
100
|
+
@patch("recce.cli.uvicorn.run")
|
|
101
|
+
@patch("recce.server.AppState")
|
|
102
|
+
def test_cmd_server_with_single_env_but_review_mode_enabled(
|
|
103
|
+
self, mock_app_state, mock_run, mock_isdir, mock_verify_required_artifacts
|
|
104
|
+
):
|
|
96
105
|
mock_verify_required_artifacts.return_value = True, None
|
|
97
|
-
self.runner.invoke(
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
106
|
+
self.runner.invoke(
|
|
107
|
+
cli_command_server,
|
|
108
|
+
[
|
|
109
|
+
"existed_state_file",
|
|
110
|
+
"--review",
|
|
111
|
+
"--target-path",
|
|
112
|
+
"existed_folder",
|
|
113
|
+
"--target-base-path",
|
|
114
|
+
"non_existed_folder",
|
|
115
|
+
],
|
|
116
|
+
)
|
|
104
117
|
mock_run.assert_called_once()
|
|
105
118
|
app_state_call_args = mock_app_state.call_args
|
|
106
|
-
app_state_flag = app_state_call_args.kwargs[
|
|
107
|
-
assert
|
|
108
|
-
assert app_state_flag[
|
|
119
|
+
app_state_flag = app_state_call_args.kwargs["flag"]
|
|
120
|
+
assert "single_env_onboarding" in app_state_flag
|
|
121
|
+
assert app_state_flag["single_env_onboarding"] is False
|
|
109
122
|
|
|
110
123
|
|
|
111
124
|
class TestCommandRun(TestCase):
|
|
@@ -113,8 +126,8 @@ class TestCommandRun(TestCase):
|
|
|
113
126
|
self.runner = CliRunner()
|
|
114
127
|
pass
|
|
115
128
|
|
|
116
|
-
@patch.object(RecceContext,
|
|
117
|
-
@patch(
|
|
129
|
+
@patch.object(RecceContext, "verify_required_artifacts")
|
|
130
|
+
@patch("recce.cli.cli_run")
|
|
118
131
|
def test_cmd_run(self, mock_cli_run, mock_verify_required_artifacts):
|
|
119
132
|
mock_verify_required_artifacts.return_value = True, None
|
|
120
133
|
|