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.

Files changed (245) hide show
  1. recce/VERSION +1 -1
  2. recce/__init__.py +27 -22
  3. recce/adapter/base.py +11 -14
  4. recce/adapter/dbt_adapter/__init__.py +845 -461
  5. recce/adapter/dbt_adapter/dbt_version.py +3 -0
  6. recce/adapter/sqlmesh_adapter.py +24 -35
  7. recce/apis/check_api.py +59 -42
  8. recce/apis/check_events_api.py +353 -0
  9. recce/apis/check_func.py +41 -35
  10. recce/apis/run_api.py +25 -19
  11. recce/apis/run_func.py +64 -25
  12. recce/artifact.py +119 -51
  13. recce/cli.py +1301 -324
  14. recce/config.py +43 -34
  15. recce/connect_to_cloud.py +138 -0
  16. recce/core.py +55 -47
  17. recce/data/404/index.html +2 -0
  18. recce/data/404.html +2 -1
  19. recce/data/__next.@lineage.!KHNsb3Qp.__PAGE__.txt +7 -0
  20. recce/data/__next.@lineage.!KHNsb3Qp.txt +4 -0
  21. recce/data/__next.__PAGE__.txt +6 -0
  22. recce/data/__next._full.txt +32 -0
  23. recce/data/__next._head.txt +8 -0
  24. recce/data/__next._index.txt +14 -0
  25. recce/data/__next._tree.txt +8 -0
  26. recce/data/_next/static/chunks/025a7e3e3f9f40ae.js +1 -0
  27. recce/data/_next/static/chunks/0ce56d67ef5779ca.js +4 -0
  28. recce/data/_next/static/chunks/1a6a78780155dac7.js +48 -0
  29. recce/data/_next/static/chunks/1de8485918b9182a.css +2 -0
  30. recce/data/_next/static/chunks/1e4b1b50d1e34993.js +1 -0
  31. recce/data/_next/static/chunks/206d5d181e4c738e.js +1 -0
  32. recce/data/_next/static/chunks/2c357efc34c5b859.js +25 -0
  33. recce/data/_next/static/chunks/2e9d95d2d48c479c.js +1 -0
  34. recce/data/_next/static/chunks/2f016dc4a3edad2e.js +2 -0
  35. recce/data/_next/static/chunks/313251962d698f7c.js +1 -0
  36. recce/data/_next/static/chunks/3a9f021f38eb5574.css +1 -0
  37. recce/data/_next/static/chunks/40079da8d2b8f651.js +1 -0
  38. recce/data/_next/static/chunks/4599182bffb64661.js +38 -0
  39. recce/data/_next/static/chunks/4e62f6e184173580.js +1 -0
  40. recce/data/_next/static/chunks/5c4dfb0d09eaa401.js +1 -0
  41. recce/data/_next/static/chunks/69e4f06ccfdfc3ac.js +1 -0
  42. recce/data/_next/static/chunks/6b206cb4707d6bee.js +1 -0
  43. recce/data/_next/static/chunks/6d8557f062aa4386.css +1 -0
  44. recce/data/_next/static/chunks/7fbe3650bd83b6b5.js +1 -0
  45. recce/data/_next/static/chunks/83fa823a825674f6.js +1 -0
  46. recce/data/_next/static/chunks/848a6c9b5f55f7ed.js +1 -0
  47. recce/data/_next/static/chunks/859462b0858aef88.css +2 -0
  48. recce/data/_next/static/chunks/923964f18c87d0f1.css +1 -0
  49. recce/data/_next/static/chunks/939390f911895d7c.js +48 -0
  50. recce/data/_next/static/chunks/99a9817237a07f43.js +1 -0
  51. recce/data/_next/static/chunks/9fed8b4b2b924054.js +5 -0
  52. recce/data/_next/static/chunks/b6949f6c5892110c.js +1 -0
  53. recce/data/_next/static/chunks/b851a1d3f8149828.js +1 -0
  54. recce/data/_next/static/chunks/c734f9ad957de0b4.js +1 -0
  55. recce/data/_next/static/chunks/cdde321b0ec75717.js +2 -0
  56. recce/data/_next/static/chunks/d0f91117d77ff844.css +1 -0
  57. recce/data/_next/static/chunks/d6c8667911c2500f.js +1 -0
  58. recce/data/_next/static/chunks/da8dab68c02752cf.js +74 -0
  59. recce/data/_next/static/chunks/dc074049c9d12d97.js +109 -0
  60. recce/data/_next/static/chunks/ee7f1a8227342421.js +1 -0
  61. recce/data/_next/static/chunks/fa2f4e56c2fccc73.js +1 -0
  62. recce/data/_next/static/chunks/turbopack-1fad664f62979b93.js +3 -0
  63. recce/data/_next/static/media/favicon.a8d38d84.ico +0 -0
  64. recce/data/_next/static/media/montserrat-cyrillic-800-normal.d80d830d.woff2 +0 -0
  65. recce/data/_next/static/media/montserrat-cyrillic-800-normal.f9d58125.woff +0 -0
  66. recce/data/_next/static/media/montserrat-cyrillic-ext-800-normal.076c2a93.woff2 +0 -0
  67. recce/data/_next/static/media/montserrat-cyrillic-ext-800-normal.a4fa76b5.woff +0 -0
  68. recce/data/_next/static/media/montserrat-latin-800-normal.cde454cc.woff2 +0 -0
  69. recce/data/_next/static/media/montserrat-latin-800-normal.d5761935.woff +0 -0
  70. recce/data/_next/static/media/montserrat-latin-ext-800-normal.40ec0659.woff2 +0 -0
  71. recce/data/_next/static/media/montserrat-latin-ext-800-normal.b671449b.woff +0 -0
  72. recce/data/_next/static/media/montserrat-vietnamese-800-normal.9f7b8541.woff +0 -0
  73. recce/data/_next/static/media/montserrat-vietnamese-800-normal.f9eb854e.woff2 +0 -0
  74. recce/data/_next/static/nX-Uz0AH6Tc6hIQUFGqaB/_buildManifest.js +11 -0
  75. recce/data/_next/static/nX-Uz0AH6Tc6hIQUFGqaB/_clientMiddlewareManifest.json +1 -0
  76. recce/data/_not-found/__next._full.txt +24 -0
  77. recce/data/_not-found/__next._head.txt +8 -0
  78. recce/data/_not-found/__next._index.txt +13 -0
  79. recce/data/_not-found/__next._not-found.__PAGE__.txt +5 -0
  80. recce/data/_not-found/__next._not-found.txt +4 -0
  81. recce/data/_not-found/__next._tree.txt +6 -0
  82. recce/data/_not-found/index.html +2 -0
  83. recce/data/_not-found/index.txt +24 -0
  84. recce/data/auth_callback.html +68 -0
  85. recce/data/checks/__next.@lineage.__DEFAULT__.txt +7 -0
  86. recce/data/checks/__next._full.txt +39 -0
  87. recce/data/checks/__next._head.txt +8 -0
  88. recce/data/checks/__next._index.txt +14 -0
  89. recce/data/checks/__next._tree.txt +8 -0
  90. recce/data/checks/__next.checks.__PAGE__.txt +10 -0
  91. recce/data/checks/__next.checks.txt +4 -0
  92. recce/data/checks/index.html +2 -0
  93. recce/data/checks/index.txt +39 -0
  94. recce/data/imgs/reload-image.svg +4 -0
  95. recce/data/index.html +2 -27
  96. recce/data/index.txt +32 -7
  97. recce/data/lineage/__next.@lineage.__DEFAULT__.txt +7 -0
  98. recce/data/lineage/__next._full.txt +39 -0
  99. recce/data/lineage/__next._head.txt +8 -0
  100. recce/data/lineage/__next._index.txt +14 -0
  101. recce/data/lineage/__next._tree.txt +8 -0
  102. recce/data/lineage/__next.lineage.__PAGE__.txt +10 -0
  103. recce/data/lineage/__next.lineage.txt +4 -0
  104. recce/data/lineage/index.html +2 -0
  105. recce/data/lineage/index.txt +39 -0
  106. recce/data/query/__next.@lineage.__DEFAULT__.txt +7 -0
  107. recce/data/query/__next._full.txt +37 -0
  108. recce/data/query/__next._head.txt +8 -0
  109. recce/data/query/__next._index.txt +14 -0
  110. recce/data/query/__next._tree.txt +8 -0
  111. recce/data/query/__next.query.__PAGE__.txt +9 -0
  112. recce/data/query/__next.query.txt +4 -0
  113. recce/data/query/index.html +2 -0
  114. recce/data/query/index.txt +37 -0
  115. recce/diff.py +6 -12
  116. recce/event/CONFIG.bak +1 -0
  117. recce/event/__init__.py +86 -74
  118. recce/event/collector.py +33 -22
  119. recce/event/track.py +49 -27
  120. recce/exceptions.py +1 -1
  121. recce/git.py +7 -7
  122. recce/github.py +57 -53
  123. recce/mcp_server.py +725 -0
  124. recce/models/__init__.py +4 -1
  125. recce/models/check.py +438 -21
  126. recce/models/run.py +1 -0
  127. recce/models/types.py +134 -28
  128. recce/pull_request.py +27 -25
  129. recce/run.py +179 -122
  130. recce/server.py +394 -104
  131. recce/state/__init__.py +31 -0
  132. recce/state/cloud.py +644 -0
  133. recce/state/const.py +26 -0
  134. recce/state/local.py +56 -0
  135. recce/state/state.py +119 -0
  136. recce/state/state_loader.py +174 -0
  137. recce/summary.py +196 -149
  138. recce/tasks/__init__.py +19 -3
  139. recce/tasks/core.py +11 -13
  140. recce/tasks/dataframe.py +82 -18
  141. recce/tasks/histogram.py +69 -34
  142. recce/tasks/lineage.py +2 -2
  143. recce/tasks/profile.py +152 -86
  144. recce/tasks/query.py +180 -89
  145. recce/tasks/rowcount.py +37 -31
  146. recce/tasks/schema.py +18 -15
  147. recce/tasks/top_k.py +35 -35
  148. recce/tasks/utils.py +147 -0
  149. recce/tasks/valuediff.py +247 -155
  150. recce/util/__init__.py +3 -0
  151. recce/util/api_token.py +80 -0
  152. recce/util/breaking.py +105 -100
  153. recce/util/cll.py +274 -219
  154. recce/util/cloud/__init__.py +15 -0
  155. recce/util/cloud/base.py +115 -0
  156. recce/util/cloud/check_events.py +190 -0
  157. recce/util/cloud/checks.py +242 -0
  158. recce/util/io.py +22 -17
  159. recce/util/lineage.py +65 -16
  160. recce/util/logger.py +1 -1
  161. recce/util/onboarding_state.py +45 -0
  162. recce/util/perf_tracking.py +85 -0
  163. recce/util/recce_cloud.py +347 -72
  164. recce/util/singleton.py +4 -4
  165. recce/util/startup_perf.py +121 -0
  166. recce/yaml/__init__.py +7 -10
  167. recce_nightly-1.30.0.20251221.dist-info/METADATA +195 -0
  168. recce_nightly-1.30.0.20251221.dist-info/RECORD +183 -0
  169. {recce_nightly-0.62.0.20250417.dist-info → recce_nightly-1.30.0.20251221.dist-info}/WHEEL +1 -2
  170. recce/data/_next/static/chunks/1f229bf6-d9fe92e56db8d93b.js +0 -1
  171. recce/data/_next/static/chunks/29e3cc0d-8c150e37dff9631b.js +0 -1
  172. recce/data/_next/static/chunks/36e1c10d-bb0210cbd6573a8d.js +0 -1
  173. recce/data/_next/static/chunks/3998a672-eaad84bdd88cc73e.js +0 -1
  174. recce/data/_next/static/chunks/450c323b-1bb5db526e54435a.js +0 -1
  175. recce/data/_next/static/chunks/47d8844f-79a1b53c66a7d7ec.js +0 -1
  176. recce/data/_next/static/chunks/500-e51c92a025a51234.js +0 -65
  177. recce/data/_next/static/chunks/6dc81886-c94b9b91bc2c3caf.js +0 -1
  178. recce/data/_next/static/chunks/700-3b65fc3666820d00.js +0 -2
  179. recce/data/_next/static/chunks/7a8a3e83-d7fa409d97b38b2b.js +0 -1
  180. recce/data/_next/static/chunks/7f27ae6c-413f6b869a04183a.js +0 -1
  181. recce/data/_next/static/chunks/9746af58-d74bef4d03eea6ab.js +0 -1
  182. recce/data/_next/static/chunks/a30376cd-7d806e1602f2dc3a.js +0 -1
  183. recce/data/_next/static/chunks/app/_not-found/page-8a886fa0855c3105.js +0 -1
  184. recce/data/_next/static/chunks/app/layout-9102e22cb73f74d6.js +0 -1
  185. recce/data/_next/static/chunks/app/page-9adc25782272ed2e.js +0 -1
  186. recce/data/_next/static/chunks/b63b1b3f-7395c74e11a14e95.js +0 -1
  187. recce/data/_next/static/chunks/c132bf7d-8102037f9ccf372a.js +0 -1
  188. recce/data/_next/static/chunks/c1ceaa8b-a1e442154d23515e.js +0 -1
  189. recce/data/_next/static/chunks/cd9f8d63-cf0d5a7b0f7a92e8.js +0 -54
  190. recce/data/_next/static/chunks/ce84277d-f42c2c58049cea2d.js +0 -1
  191. recce/data/_next/static/chunks/e24bf851-0f8cbc99656833e7.js +0 -1
  192. recce/data/_next/static/chunks/fee69bc6-f17d36c080742e74.js +0 -1
  193. recce/data/_next/static/chunks/framework-ded83d71b51ce901.js +0 -1
  194. recce/data/_next/static/chunks/main-a0859f1f36d0aa6c.js +0 -1
  195. recce/data/_next/static/chunks/main-app-0225a2255968e566.js +0 -1
  196. recce/data/_next/static/chunks/pages/_app-d5672bf3d8b6371b.js +0 -1
  197. recce/data/_next/static/chunks/pages/_error-ed75be3f25588548.js +0 -1
  198. recce/data/_next/static/chunks/webpack-567d72f0bc0820d5.js +0 -1
  199. recce/data/_next/static/css/c9ecb46a4b21c126.css +0 -14
  200. recce/data/_next/static/media/montserrat-cyrillic-800-normal.22628180.woff2 +0 -0
  201. recce/data/_next/static/media/montserrat-cyrillic-800-normal.31d693bb.woff +0 -0
  202. recce/data/_next/static/media/montserrat-cyrillic-ext-800-normal.7e2c1e62.woff +0 -0
  203. recce/data/_next/static/media/montserrat-cyrillic-ext-800-normal.94a63aea.woff2 +0 -0
  204. recce/data/_next/static/media/montserrat-latin-800-normal.6f8fa298.woff2 +0 -0
  205. recce/data/_next/static/media/montserrat-latin-800-normal.97e20d5e.woff +0 -0
  206. recce/data/_next/static/media/montserrat-latin-ext-800-normal.013b84f9.woff2 +0 -0
  207. recce/data/_next/static/media/montserrat-latin-ext-800-normal.aff52ab0.woff +0 -0
  208. recce/data/_next/static/media/montserrat-vietnamese-800-normal.5f21869b.woff +0 -0
  209. recce/data/_next/static/media/montserrat-vietnamese-800-normal.c0035377.woff2 +0 -0
  210. recce/data/_next/static/qiyFlux77VkhxiceAJe_F/_buildManifest.js +0 -1
  211. recce/state.py +0 -753
  212. recce_nightly-0.62.0.20250417.dist-info/METADATA +0 -311
  213. recce_nightly-0.62.0.20250417.dist-info/RECORD +0 -139
  214. recce_nightly-0.62.0.20250417.dist-info/top_level.txt +0 -2
  215. tests/__init__.py +0 -0
  216. tests/adapter/__init__.py +0 -0
  217. tests/adapter/dbt_adapter/__init__.py +0 -0
  218. tests/adapter/dbt_adapter/conftest.py +0 -13
  219. tests/adapter/dbt_adapter/dbt_test_helper.py +0 -283
  220. tests/adapter/dbt_adapter/test_dbt_adapter.py +0 -40
  221. tests/adapter/dbt_adapter/test_dbt_cll.py +0 -102
  222. tests/adapter/dbt_adapter/test_selector.py +0 -177
  223. tests/tasks/__init__.py +0 -0
  224. tests/tasks/conftest.py +0 -4
  225. tests/tasks/test_histogram.py +0 -137
  226. tests/tasks/test_lineage.py +0 -42
  227. tests/tasks/test_preset_checks.py +0 -50
  228. tests/tasks/test_profile.py +0 -73
  229. tests/tasks/test_query.py +0 -151
  230. tests/tasks/test_row_count.py +0 -116
  231. tests/tasks/test_schema.py +0 -99
  232. tests/tasks/test_top_k.py +0 -73
  233. tests/tasks/test_valuediff.py +0 -74
  234. tests/test_cli.py +0 -122
  235. tests/test_config.py +0 -45
  236. tests/test_core.py +0 -27
  237. tests/test_dbt.py +0 -36
  238. tests/test_pull_request.py +0 -130
  239. tests/test_server.py +0 -98
  240. tests/test_state.py +0 -123
  241. tests/test_summary.py +0 -57
  242. /recce/data/_next/static/chunks/{polyfills-42372ed130431b0a.js → a6dad97d9634a72d.js} +0 -0
  243. /recce/data/_next/static/{qiyFlux77VkhxiceAJe_F → nX-Uz0AH6Tc6hIQUFGqaB}/_ssgManifest.js +0 -0
  244. {recce_nightly-0.62.0.20250417.dist-info → recce_nightly-1.30.0.20251221.dist-info}/entry_points.txt +0 -0
  245. {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({})
@@ -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
@@ -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')