datajunction-ui 0.0.1-rc.9 → 0.0.2-0.dev1

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.
Files changed (244) hide show
  1. package/.env +2 -0
  2. package/.prettierignore +3 -1
  3. package/Makefile +9 -0
  4. package/cleanup-deps.sh +70 -0
  5. package/dj-logo.svg +10 -0
  6. package/package.json +53 -14
  7. package/public/favicon.ico +0 -0
  8. package/public/index.html +1 -1
  9. package/runit.sh +30 -0
  10. package/runit2.sh +30 -0
  11. package/src/__tests__/reportWebVitals.test.jsx +44 -0
  12. package/src/app/__tests__/__snapshots__/index.test.tsx.snap +5 -109
  13. package/src/app/components/AddNodeDropdown.jsx +44 -0
  14. package/src/app/components/ListGroupItem.jsx +9 -1
  15. package/src/app/components/NamespaceHeader.jsx +4 -13
  16. package/src/app/components/NodeListActions.jsx +69 -0
  17. package/src/app/components/NodeMaterializationDelete.jsx +90 -0
  18. package/src/app/components/NotificationBell.tsx +223 -0
  19. package/src/app/components/QueryInfo.jsx +172 -0
  20. package/src/app/components/Search.jsx +94 -0
  21. package/src/app/components/Tab.jsx +8 -1
  22. package/src/app/components/ToggleSwitch.jsx +20 -0
  23. package/src/app/components/UserMenu.tsx +100 -0
  24. package/src/app/components/__tests__/NodeListActions.test.jsx +94 -0
  25. package/src/app/components/__tests__/NodeMaterializationDelete.test.jsx +263 -0
  26. package/src/app/components/__tests__/NotificationBell.test.tsx +302 -0
  27. package/src/app/components/__tests__/QueryInfo.test.jsx +183 -0
  28. package/src/app/components/__tests__/Search.test.jsx +307 -0
  29. package/src/app/components/__tests__/Tab.test.jsx +27 -0
  30. package/src/app/components/__tests__/ToggleSwitch.test.jsx +43 -0
  31. package/src/app/components/__tests__/UserMenu.test.tsx +241 -0
  32. package/src/app/components/__tests__/__snapshots__/ListGroupItem.test.tsx.snap +8 -3
  33. package/src/app/components/__tests__/__snapshots__/NamespaceHeader.test.jsx.snap +2 -18
  34. package/src/app/components/djgraph/Collapse.jsx +47 -0
  35. package/src/app/components/djgraph/DJNode.jsx +61 -83
  36. package/src/app/components/djgraph/DJNodeColumns.jsx +75 -0
  37. package/src/app/components/djgraph/DJNodeDimensions.jsx +75 -0
  38. package/src/app/components/djgraph/LayoutFlow.jsx +106 -0
  39. package/src/app/components/djgraph/__tests__/Collapse.test.jsx +51 -0
  40. package/src/app/components/djgraph/__tests__/DJNodeColumns.test.jsx +83 -0
  41. package/src/app/components/djgraph/__tests__/DJNodeDimensions.test.jsx +118 -0
  42. package/src/app/components/djgraph/__tests__/__snapshots__/DJNode.test.tsx.snap +84 -40
  43. package/src/app/components/forms/Action.jsx +8 -0
  44. package/src/app/components/forms/NodeNameField.jsx +64 -0
  45. package/src/app/components/search.css +17 -0
  46. package/src/app/constants.js +2 -0
  47. package/src/app/icons/AddItemIcon.jsx +16 -0
  48. package/src/app/icons/AlertIcon.jsx +33 -0
  49. package/src/app/icons/CollapsedIcon.jsx +15 -0
  50. package/src/app/icons/CommitIcon.jsx +45 -0
  51. package/src/app/icons/DJLogo.jsx +36 -0
  52. package/src/app/icons/DeleteIcon.jsx +21 -0
  53. package/src/app/icons/DiffIcon.jsx +63 -0
  54. package/src/app/icons/EditIcon.jsx +18 -0
  55. package/src/app/icons/ExpandedIcon.jsx +15 -0
  56. package/src/app/icons/EyeIcon.jsx +20 -0
  57. package/src/app/icons/FilterIcon.jsx +7 -0
  58. package/src/app/icons/HorizontalHierarchyIcon.jsx +15 -0
  59. package/src/app/icons/InvalidIcon.jsx +16 -0
  60. package/src/app/icons/JupyterExportIcon.jsx +25 -0
  61. package/src/app/icons/LoadingIcon.jsx +14 -0
  62. package/src/app/icons/NodeIcon.jsx +49 -0
  63. package/src/app/icons/NotificationIcon.jsx +27 -0
  64. package/src/app/icons/PythonIcon.jsx +14 -0
  65. package/src/app/icons/SettingsIcon.jsx +28 -0
  66. package/src/app/icons/TableIcon.jsx +14 -0
  67. package/src/app/icons/ValidIcon.jsx +16 -0
  68. package/src/app/icons/WrenchIcon.jsx +36 -0
  69. package/src/app/index.tsx +130 -37
  70. package/src/app/pages/AddEditNodePage/AlertMessage.jsx +10 -0
  71. package/src/app/pages/AddEditNodePage/ColumnMetadata.jsx +61 -0
  72. package/src/app/pages/AddEditNodePage/ColumnsMetadataInput.jsx +72 -0
  73. package/src/app/pages/AddEditNodePage/ColumnsSelect.jsx +84 -0
  74. package/src/app/pages/AddEditNodePage/CustomMetadataField.jsx +144 -0
  75. package/src/app/pages/AddEditNodePage/DescriptionField.jsx +17 -0
  76. package/src/app/pages/AddEditNodePage/DisplayNameField.jsx +16 -0
  77. package/src/app/pages/AddEditNodePage/ExperimentationExtension.jsx +338 -0
  78. package/src/app/pages/AddEditNodePage/FormikSelect.jsx +64 -0
  79. package/src/app/pages/AddEditNodePage/FullNameField.jsx +38 -0
  80. package/src/app/pages/AddEditNodePage/Loadable.jsx +20 -0
  81. package/src/app/pages/AddEditNodePage/MetricMetadataFields.jsx +75 -0
  82. package/src/app/pages/AddEditNodePage/MetricQueryField.jsx +71 -0
  83. package/src/app/pages/AddEditNodePage/NamespaceField.jsx +40 -0
  84. package/src/app/pages/AddEditNodePage/NodeModeField.jsx +14 -0
  85. package/src/app/pages/AddEditNodePage/NodeQueryField.jsx +94 -0
  86. package/src/app/pages/AddEditNodePage/OwnersField.jsx +54 -0
  87. package/src/app/pages/AddEditNodePage/RequiredDimensionsSelect.jsx +54 -0
  88. package/src/app/pages/AddEditNodePage/TagsField.jsx +47 -0
  89. package/src/app/pages/AddEditNodePage/UpstreamNodeField.jsx +49 -0
  90. package/src/app/pages/AddEditNodePage/__tests__/AddEditNodePageFormFailed.test.jsx +110 -0
  91. package/src/app/pages/AddEditNodePage/__tests__/AddEditNodePageFormSuccess.test.jsx +291 -0
  92. package/src/app/pages/AddEditNodePage/__tests__/FormikSelect.test.jsx +75 -0
  93. package/src/app/pages/AddEditNodePage/__tests__/FullNameField.test.jsx +31 -0
  94. package/src/app/pages/AddEditNodePage/__tests__/NodeQueryField.test.jsx +30 -0
  95. package/src/app/pages/AddEditNodePage/__tests__/__snapshots__/AddEditNodePageFormFailed.test.jsx.snap +54 -0
  96. package/src/app/pages/AddEditNodePage/__tests__/__snapshots__/AddEditNodePageFormSuccess.test.jsx.snap +3 -0
  97. package/src/app/pages/AddEditNodePage/__tests__/__snapshots__/index.test.jsx.snap +3 -0
  98. package/src/app/pages/AddEditNodePage/__tests__/index.test.jsx +224 -0
  99. package/src/app/pages/AddEditNodePage/index.jsx +545 -0
  100. package/src/app/pages/AddEditTagPage/Loadable.jsx +16 -0
  101. package/src/app/pages/AddEditTagPage/__tests__/AddEditTagPage.test.jsx +107 -0
  102. package/src/app/pages/AddEditTagPage/index.jsx +132 -0
  103. package/src/app/pages/CubeBuilderPage/DimensionsSelect.jsx +152 -0
  104. package/src/app/pages/CubeBuilderPage/Loadable.jsx +16 -0
  105. package/src/app/pages/CubeBuilderPage/MetricsSelect.jsx +75 -0
  106. package/src/app/pages/CubeBuilderPage/__tests__/index.test.jsx +373 -0
  107. package/src/app/pages/CubeBuilderPage/index.jsx +291 -0
  108. package/src/app/pages/LoginPage/LoginForm.jsx +124 -0
  109. package/src/app/pages/LoginPage/SignupForm.jsx +156 -0
  110. package/src/app/pages/LoginPage/__tests__/index.test.jsx +97 -0
  111. package/src/app/pages/LoginPage/assets/sign-in-with-github.png +0 -0
  112. package/src/app/pages/LoginPage/assets/sign-in-with-google.png +0 -0
  113. package/src/app/pages/LoginPage/index.jsx +17 -0
  114. package/src/app/pages/NamespacePage/AddNamespacePopover.jsx +85 -0
  115. package/src/app/pages/NamespacePage/Explorer.jsx +232 -0
  116. package/src/app/pages/NamespacePage/FieldControl.jsx +21 -0
  117. package/src/app/pages/NamespacePage/NodeModeSelect.jsx +27 -0
  118. package/src/app/pages/NamespacePage/NodeTypeSelect.jsx +30 -0
  119. package/src/app/pages/NamespacePage/TagSelect.jsx +44 -0
  120. package/src/app/pages/NamespacePage/UserSelect.jsx +47 -0
  121. package/src/app/pages/NamespacePage/__tests__/AddNamespacePopover.test.jsx +283 -0
  122. package/src/app/pages/NamespacePage/__tests__/index.test.jsx +331 -0
  123. package/src/app/pages/NamespacePage/index.jsx +356 -42
  124. package/src/app/pages/NodePage/AddBackfillPopover.jsx +165 -0
  125. package/src/app/pages/NodePage/AddComplexDimensionLinkPopover.jsx +367 -0
  126. package/src/app/pages/NodePage/AddMaterializationPopover.jsx +222 -0
  127. package/src/app/pages/NodePage/AvailabilityStateBlock.jsx +67 -0
  128. package/src/app/pages/NodePage/ClientCodePopover.jsx +94 -0
  129. package/src/app/pages/NodePage/DimensionFilter.jsx +86 -0
  130. package/src/app/pages/NodePage/EditColumnDescriptionPopover.jsx +116 -0
  131. package/src/app/pages/NodePage/EditColumnPopover.jsx +116 -0
  132. package/src/app/pages/NodePage/LinkDimensionPopover.jsx +164 -0
  133. package/src/app/pages/NodePage/ManageDimensionLinksDialog.jsx +526 -0
  134. package/src/app/pages/NodePage/MaterializationConfigField.jsx +60 -0
  135. package/src/app/pages/NodePage/NodeColumnTab.jsx +421 -30
  136. package/src/app/pages/NodePage/NodeDependenciesTab.jsx +153 -0
  137. package/src/app/pages/NodePage/NodeGraphTab.jsx +119 -148
  138. package/src/app/pages/NodePage/NodeHistory.jsx +236 -0
  139. package/src/app/pages/NodePage/NodeInfoTab.jsx +346 -49
  140. package/src/app/pages/NodePage/NodeLineageTab.jsx +84 -0
  141. package/src/app/pages/NodePage/NodeMaterializationTab.jsx +585 -0
  142. package/src/app/pages/NodePage/NodeRevisionMaterializationTab.jsx +58 -0
  143. package/src/app/pages/NodePage/NodeStatus.jsx +100 -31
  144. package/src/app/pages/NodePage/NodeValidateTab.jsx +367 -0
  145. package/src/app/pages/NodePage/NodesWithDimension.jsx +42 -0
  146. package/src/app/pages/NodePage/NotebookDownload.jsx +36 -0
  147. package/src/app/pages/NodePage/PartitionColumnPopover.jsx +151 -0
  148. package/src/app/pages/NodePage/PartitionValueForm.jsx +60 -0
  149. package/src/app/pages/NodePage/RevisionDiff.jsx +209 -0
  150. package/src/app/pages/NodePage/WatchNodeButton.jsx +226 -0
  151. package/src/app/pages/NodePage/__tests__/AddBackfillPopover.test.jsx +56 -0
  152. package/src/app/pages/NodePage/__tests__/AddComplexDimensionLinkPopover.test.jsx +459 -0
  153. package/src/app/pages/NodePage/__tests__/AddMaterializationPopover.test.jsx +87 -0
  154. package/src/app/pages/NodePage/__tests__/DimensionFilter.test.jsx +74 -0
  155. package/src/app/pages/NodePage/__tests__/EditColumnDescriptionPopover.test.jsx +149 -0
  156. package/src/app/pages/NodePage/__tests__/EditColumnPopover.test.jsx +144 -0
  157. package/src/app/pages/NodePage/__tests__/LinkDimensionPopover.test.jsx +132 -0
  158. package/src/app/pages/NodePage/__tests__/ManageDimensionLinksDialog.test.jsx +390 -0
  159. package/src/app/pages/NodePage/__tests__/NodeColumnTab.test.jsx +166 -0
  160. package/src/app/pages/NodePage/__tests__/NodeDependenciesTab.test.jsx +151 -0
  161. package/src/app/pages/NodePage/__tests__/NodeGraphTab.test.jsx +595 -0
  162. package/src/app/pages/NodePage/__tests__/NodeLineageTab.test.jsx +58 -0
  163. package/src/app/pages/NodePage/__tests__/NodeMaterializationTab.test.jsx +190 -0
  164. package/src/app/pages/NodePage/__tests__/NodePage.test.jsx +882 -0
  165. package/src/app/pages/NodePage/__tests__/NodeWithDimension.test.jsx +175 -0
  166. package/src/app/pages/NodePage/__tests__/RevisionDiff.test.jsx +164 -0
  167. package/src/app/pages/NodePage/__tests__/__snapshots__/NodePage.test.jsx.snap +19 -0
  168. package/src/app/pages/NodePage/index.jsx +190 -44
  169. package/src/app/pages/NotFoundPage/__tests__/index.test.jsx +16 -0
  170. package/src/app/pages/NotificationsPage/Loadable.jsx +6 -0
  171. package/src/app/pages/NotificationsPage/__tests__/index.test.jsx +287 -0
  172. package/src/app/pages/NotificationsPage/index.jsx +136 -0
  173. package/src/app/pages/OverviewPage/ByStatusPanel.jsx +69 -0
  174. package/src/app/pages/OverviewPage/DimensionNodeUsagePanel.jsx +48 -0
  175. package/src/app/pages/OverviewPage/GovernanceWarningsPanel.jsx +107 -0
  176. package/src/app/pages/OverviewPage/Loadable.jsx +16 -0
  177. package/src/app/pages/OverviewPage/NodesByTypePanel.jsx +63 -0
  178. package/src/app/pages/OverviewPage/OverviewPanel.jsx +94 -0
  179. package/src/app/pages/OverviewPage/TrendsPanel.jsx +66 -0
  180. package/src/app/pages/OverviewPage/__tests__/ByStatusPanel.test.jsx +36 -0
  181. package/src/app/pages/OverviewPage/__tests__/DimensionNodeUsagePanel.test.jsx +76 -0
  182. package/src/app/pages/OverviewPage/__tests__/GovernanceWarningsPanel.test.jsx +77 -0
  183. package/src/app/pages/OverviewPage/__tests__/NodesByTypePanel.test.jsx +86 -0
  184. package/src/app/pages/OverviewPage/__tests__/OverviewPanel.test.jsx +78 -0
  185. package/src/app/pages/OverviewPage/__tests__/TrendsPanel.test.jsx +120 -0
  186. package/src/app/pages/OverviewPage/__tests__/index.test.jsx +54 -0
  187. package/src/app/pages/OverviewPage/index.jsx +22 -0
  188. package/src/app/pages/RegisterTablePage/Loadable.jsx +16 -0
  189. package/src/app/pages/RegisterTablePage/__tests__/RegisterTablePage.test.jsx +112 -0
  190. package/src/app/pages/RegisterTablePage/__tests__/__snapshots__/RegisterTablePage.test.jsx.snap +38 -0
  191. package/src/app/pages/RegisterTablePage/index.jsx +142 -0
  192. package/src/app/pages/Root/__tests__/index.test.jsx +44 -0
  193. package/src/app/pages/Root/index.tsx +92 -10
  194. package/src/app/pages/SQLBuilderPage/Loadable.jsx +16 -0
  195. package/src/app/pages/SQLBuilderPage/__tests__/index.test.jsx +173 -0
  196. package/src/app/pages/SQLBuilderPage/index.jsx +390 -0
  197. package/src/app/pages/SettingsPage/CreateServiceAccountModal.jsx +152 -0
  198. package/src/app/pages/SettingsPage/Loadable.jsx +16 -0
  199. package/src/app/pages/SettingsPage/NotificationSubscriptionsSection.jsx +189 -0
  200. package/src/app/pages/SettingsPage/ProfileSection.jsx +41 -0
  201. package/src/app/pages/SettingsPage/ServiceAccountsSection.jsx +95 -0
  202. package/src/app/pages/SettingsPage/__tests__/CreateServiceAccountModal.test.jsx +318 -0
  203. package/src/app/pages/SettingsPage/__tests__/NotificationSubscriptionsSection.test.jsx +233 -0
  204. package/src/app/pages/SettingsPage/__tests__/ProfileSection.test.jsx +65 -0
  205. package/src/app/pages/SettingsPage/__tests__/ServiceAccountsSection.test.jsx +150 -0
  206. package/src/app/pages/SettingsPage/__tests__/index.test.jsx +184 -0
  207. package/src/app/pages/SettingsPage/index.jsx +148 -0
  208. package/src/app/pages/TagPage/Loadable.jsx +16 -0
  209. package/src/app/pages/TagPage/__tests__/TagPage.test.jsx +70 -0
  210. package/src/app/pages/TagPage/index.jsx +79 -0
  211. package/src/app/services/DJService.js +1444 -21
  212. package/src/app/services/__tests__/DJService.test.jsx +2118 -0
  213. package/src/app/utils/__tests__/date.test.js +198 -0
  214. package/src/app/utils/date.js +65 -0
  215. package/src/index.tsx +1 -0
  216. package/src/mocks/mockNodes.jsx +1477 -0
  217. package/src/setupTests.ts +31 -1
  218. package/src/styles/dag.css +117 -5
  219. package/src/styles/index.css +1028 -31
  220. package/src/styles/loading.css +34 -0
  221. package/src/styles/login.css +81 -0
  222. package/src/styles/nav-bar.css +274 -0
  223. package/src/styles/node-creation.scss +276 -0
  224. package/src/styles/node-list.css +4 -0
  225. package/src/styles/overview.css +72 -0
  226. package/src/styles/settings.css +787 -0
  227. package/src/styles/sorted-table.css +15 -0
  228. package/src/styles/styles.scss +44 -0
  229. package/src/styles/styles.scss.d.ts +9 -0
  230. package/src/utils/form.jsx +23 -0
  231. package/webpack.config.js +17 -6
  232. package/.babelrc +0 -4
  233. package/.env.local +0 -4
  234. package/.env.production +0 -1
  235. package/.github/pull_request_template.md +0 -11
  236. package/.github/workflows/ci.yml +0 -33
  237. package/.vscode/extensions.json +0 -7
  238. package/.vscode/launch.json +0 -15
  239. package/.vscode/settings.json +0 -25
  240. package/Dockerfile +0 -7
  241. package/src/app/pages/ListNamespacesPage/Loadable.jsx +0 -14
  242. package/src/app/pages/ListNamespacesPage/index.jsx +0 -62
  243. package/src/app/pages/NamespacePage/__tests__/__snapshots__/index.test.tsx.snap +0 -45
  244. package/src/app/pages/NamespacePage/__tests__/index.test.tsx +0 -14
@@ -0,0 +1,2118 @@
1
+ import { DataJunctionAPI } from '../DJService';
2
+ import { mocks } from '../../../mocks/mockNodes';
3
+
4
+ describe('DataJunctionAPI', () => {
5
+ let originalEventSource;
6
+
7
+ const DJ_URL = 'http://localhost:8000';
8
+ process.env.REACT_APP_DJ_URL = DJ_URL;
9
+
10
+ beforeEach(() => {
11
+ fetch.resetMocks();
12
+ originalEventSource = global.EventSource;
13
+ global.EventSource = jest.fn();
14
+ });
15
+
16
+ afterEach(() => {
17
+ global.EventSource = originalEventSource;
18
+ });
19
+
20
+ it('calls whoami correctly', async () => {
21
+ fetch.mockResponseOnce(JSON.stringify({}));
22
+ await DataJunctionAPI.whoami();
23
+ expect(fetch).toHaveBeenCalledWith(`${DJ_URL}/whoami/`, {
24
+ credentials: 'include',
25
+ });
26
+ });
27
+
28
+ it('calls logout correctly', async () => {
29
+ fetch.mockResponseOnce(JSON.stringify({}));
30
+ await DataJunctionAPI.logout();
31
+ expect(fetch).toHaveBeenCalledWith(`${DJ_URL}/logout/`, {
32
+ credentials: 'include',
33
+ method: 'POST',
34
+ });
35
+ });
36
+
37
+ it('calls catalogs correctly', async () => {
38
+ fetch.mockResponseOnce(JSON.stringify({}));
39
+ await DataJunctionAPI.catalogs();
40
+ expect(fetch).toHaveBeenCalledWith(`${DJ_URL}/catalogs`, {
41
+ credentials: 'include',
42
+ });
43
+ });
44
+
45
+ it('calls engines correctly', async () => {
46
+ fetch.mockResponseOnce(JSON.stringify({}));
47
+ await DataJunctionAPI.engines();
48
+ expect(fetch).toHaveBeenCalledWith(`${DJ_URL}/engines`, {
49
+ credentials: 'include',
50
+ });
51
+ });
52
+
53
+ it('calls node correctly', async () => {
54
+ fetch.mockResponseOnce(JSON.stringify(mocks.mockMetricNode));
55
+ const nodeData = await DataJunctionAPI.node(mocks.mockMetricNode.name);
56
+ expect(fetch).toHaveBeenCalledWith(
57
+ `${DJ_URL}/nodes/${mocks.mockMetricNode.name}/`,
58
+ {
59
+ credentials: 'include',
60
+ },
61
+ );
62
+ expect(nodeData.name).toEqual(mocks.mockMetricNode.name);
63
+ expect(nodeData.display_name).toEqual(mocks.mockMetricNode.display_name);
64
+ expect(nodeData.type).toEqual(mocks.mockMetricNode.type);
65
+ expect(nodeData.primary_key).toEqual([]);
66
+ });
67
+
68
+ it('calls nodeDetails correctly', async () => {
69
+ fetch.mockResponseOnce(JSON.stringify([mocks.mockMetricNode]));
70
+ const nodeData = await DataJunctionAPI.nodeDetails();
71
+ expect(fetch).toHaveBeenCalledWith(`${DJ_URL}/nodes/details/`, {
72
+ credentials: 'include',
73
+ });
74
+ expect(nodeData).toEqual([mocks.mockMetricNode]);
75
+ });
76
+
77
+ it('calls validate correctly', async () => {
78
+ fetch.mockResponseOnce(JSON.stringify([mocks.mockMetricNode]));
79
+ await DataJunctionAPI.validateNode(
80
+ 'metric',
81
+ 'default.num_repair_orders',
82
+ 'aa',
83
+ 'desc',
84
+ 'select 1',
85
+ );
86
+ expect(fetch).toHaveBeenCalledWith(`${DJ_URL}/nodes/validate`, {
87
+ credentials: 'include',
88
+ body: JSON.stringify({
89
+ name: 'default.num_repair_orders',
90
+ display_name: 'aa',
91
+ description: 'desc',
92
+ query: 'select 1',
93
+ type: 'metric',
94
+ mode: 'published',
95
+ }),
96
+
97
+ headers: {
98
+ 'Content-Type': 'application/json',
99
+ },
100
+ method: 'POST',
101
+ });
102
+ });
103
+
104
+ it('calls registerTable correctly', async () => {
105
+ fetch.mockResponseOnce(JSON.stringify([mocks.mockMetricNode]));
106
+ await DataJunctionAPI.registerTable('default', 'xyz', 'abc');
107
+ expect(fetch).toHaveBeenCalledWith(
108
+ `${DJ_URL}/register/table/default/xyz/abc`,
109
+ {
110
+ credentials: 'include',
111
+ headers: {
112
+ 'Content-Type': 'application/json',
113
+ },
114
+ method: 'POST',
115
+ },
116
+ );
117
+ });
118
+
119
+ it('node with errors are handled', async () => {
120
+ const mockNotFound = { message: 'node not found' };
121
+ fetch.mockResponseOnce(JSON.stringify(mockNotFound));
122
+ const nodeData = await DataJunctionAPI.node(mocks.mockMetricNode.name);
123
+ expect(nodeData.message).toEqual(mockNotFound.message);
124
+ });
125
+
126
+ it('calls nodes correctly', async () => {
127
+ const prefix = 'samplePrefix';
128
+ fetch.mockResponseOnce(JSON.stringify({}));
129
+ await DataJunctionAPI.nodes(prefix);
130
+ expect(fetch).toHaveBeenCalledWith(`${DJ_URL}/nodes/?prefix=${prefix}`, {
131
+ credentials: 'include',
132
+ });
133
+ });
134
+
135
+ it('calls nodesWithType correctly', async () => {
136
+ const nodeType = 'transform';
137
+ fetch.mockResponseOnce(JSON.stringify({}));
138
+ await DataJunctionAPI.nodesWithType(nodeType);
139
+ expect(fetch).toHaveBeenCalledWith(
140
+ `${DJ_URL}/nodes/?node_type=${nodeType}`,
141
+ {
142
+ credentials: 'include',
143
+ },
144
+ );
145
+ });
146
+
147
+ it('calls createNode correctly', async () => {
148
+ const sampleArgs = [
149
+ 'type',
150
+ 'name',
151
+ 'display_name',
152
+ 'description',
153
+ 'query',
154
+ 'mode',
155
+ 'namespace',
156
+ 'primary_key',
157
+ undefined, // metric_direction
158
+ undefined, // metric_unit
159
+ undefined, // required_dimensions
160
+ { key: 'value' }, // custom_metadata
161
+ ];
162
+ fetch.mockResponseOnce(JSON.stringify({}));
163
+ await DataJunctionAPI.createNode(...sampleArgs);
164
+ expect(fetch).toHaveBeenCalledWith(`${DJ_URL}/nodes/${sampleArgs[0]}`, {
165
+ method: 'POST',
166
+ headers: {
167
+ 'Content-Type': 'application/json',
168
+ },
169
+ body: JSON.stringify({
170
+ name: sampleArgs[1],
171
+ display_name: sampleArgs[2],
172
+ description: sampleArgs[3],
173
+ query: sampleArgs[4],
174
+ mode: sampleArgs[5],
175
+ namespace: sampleArgs[6],
176
+ primary_key: sampleArgs[7],
177
+ metric_metadata: null,
178
+ required_dimensions: undefined,
179
+ custom_metadata: { key: 'value' },
180
+ }),
181
+ credentials: 'include',
182
+ });
183
+ });
184
+
185
+ it('calls patchNode correctly', async () => {
186
+ const sampleArgs = [
187
+ 'name',
188
+ 'display_name',
189
+ 'description',
190
+ 'query',
191
+ 'mode',
192
+ 'primary_key',
193
+ 'neutral',
194
+ '',
195
+ null, // significant_digits
196
+ undefined, // required_dimensions
197
+ undefined, // owners
198
+ { key: 'value' }, // custom_metadata
199
+ ];
200
+ fetch.mockResponseOnce(JSON.stringify({}));
201
+ await DataJunctionAPI.patchNode(...sampleArgs);
202
+ expect(fetch).toHaveBeenCalledWith(`${DJ_URL}/nodes/${sampleArgs[0]}`, {
203
+ method: 'PATCH',
204
+ headers: {
205
+ 'Content-Type': 'application/json',
206
+ },
207
+ body: JSON.stringify({
208
+ display_name: sampleArgs[1],
209
+ description: sampleArgs[2],
210
+ query: sampleArgs[3],
211
+ mode: sampleArgs[4],
212
+ primary_key: sampleArgs[5],
213
+ metric_metadata: {
214
+ direction: 'neutral',
215
+ unit: '',
216
+ significant_digits: null,
217
+ },
218
+ required_dimensions: undefined,
219
+ owners: undefined,
220
+ custom_metadata: { key: 'value' },
221
+ }),
222
+ credentials: 'include',
223
+ });
224
+
225
+ fetch.mockResponseOnce([
226
+ JSON.stringify({ message: 'Update failed' }),
227
+ { status: 200 },
228
+ ]);
229
+ const response = await DataJunctionAPI.patchNode(...sampleArgs);
230
+ expect(response).toEqual({
231
+ json: { message: 'Update failed' },
232
+ status: 500,
233
+ });
234
+ });
235
+
236
+ it('calls createCube correctly', async () => {
237
+ const sampleArgs = [
238
+ 'default.node_name',
239
+ 'Node Display Name',
240
+ 'Some readable description',
241
+ 'draft',
242
+ ['default.num_repair_orders'],
243
+ [
244
+ 'default.date_dim.year',
245
+ 'default.date_dim.month',
246
+ 'default.date_dim.day',
247
+ ],
248
+ [],
249
+ ];
250
+ fetch.mockResponseOnce(JSON.stringify({}));
251
+ await DataJunctionAPI.createCube(...sampleArgs);
252
+ expect(fetch).toHaveBeenCalledWith(`${DJ_URL}/nodes/cube`, {
253
+ method: 'POST',
254
+ headers: {
255
+ 'Content-Type': 'application/json',
256
+ },
257
+ body: JSON.stringify({
258
+ name: sampleArgs[0],
259
+ display_name: sampleArgs[1],
260
+ description: sampleArgs[2],
261
+ metrics: sampleArgs[4],
262
+ dimensions: sampleArgs[5],
263
+ filters: sampleArgs[6],
264
+ mode: sampleArgs[3],
265
+ }),
266
+ credentials: 'include',
267
+ });
268
+ });
269
+
270
+ it('calls patchCube correctly', async () => {
271
+ const sampleArgs = [
272
+ 'default.node_name',
273
+ 'Node Display Name',
274
+ 'Some readable description',
275
+ 'draft',
276
+ ['default.num_repair_orders'],
277
+ [
278
+ 'default.date_dim.year',
279
+ 'default.date_dim.month',
280
+ 'default.date_dim.day',
281
+ ],
282
+ [],
283
+ ];
284
+ fetch.mockResponseOnce(JSON.stringify({}));
285
+ await DataJunctionAPI.patchCube(...sampleArgs);
286
+ expect(fetch).toHaveBeenCalledWith(`${DJ_URL}/nodes/default.node_name`, {
287
+ method: 'PATCH',
288
+ headers: {
289
+ 'Content-Type': 'application/json',
290
+ },
291
+ body: JSON.stringify({
292
+ display_name: sampleArgs[1],
293
+ description: sampleArgs[2],
294
+ metrics: sampleArgs[4],
295
+ dimensions: sampleArgs[5],
296
+ filters: sampleArgs[6],
297
+ mode: sampleArgs[3],
298
+ }),
299
+ credentials: 'include',
300
+ });
301
+ });
302
+
303
+ it('calls upstreams correctly', async () => {
304
+ const nodeName = 'sampleNode';
305
+ fetch.mockResponseOnce(JSON.stringify({}));
306
+ await DataJunctionAPI.upstreams(nodeName);
307
+ expect(fetch).toHaveBeenCalledWith(
308
+ `${DJ_URL}/nodes/${nodeName}/upstream/`,
309
+ {
310
+ credentials: 'include',
311
+ },
312
+ );
313
+ });
314
+
315
+ it('calls downstreams correctly', async () => {
316
+ const nodeName = 'sampleNode';
317
+ fetch.mockResponseOnce(JSON.stringify({}));
318
+ await DataJunctionAPI.downstreams(nodeName);
319
+ expect(fetch).toHaveBeenCalledWith(
320
+ `${DJ_URL}/nodes/${nodeName}/downstream/`,
321
+ {
322
+ credentials: 'include',
323
+ },
324
+ );
325
+ });
326
+
327
+ it('calls node_dag correctly', async () => {
328
+ const nodeName = 'sampleNode';
329
+ fetch.mockResponseOnce(JSON.stringify({}));
330
+ await DataJunctionAPI.node_dag(nodeName);
331
+ expect(fetch).toHaveBeenCalledWith(`${DJ_URL}/nodes/${nodeName}/dag/`, {
332
+ credentials: 'include',
333
+ });
334
+ });
335
+
336
+ it('calls node_lineage correctly', async () => {
337
+ const nodeName = 'sampleNode';
338
+ fetch.mockResponseOnce(JSON.stringify({}));
339
+ await DataJunctionAPI.node_lineage(nodeName);
340
+ expect(fetch).toHaveBeenCalledWith(`${DJ_URL}/nodes/${nodeName}/lineage/`, {
341
+ credentials: 'include',
342
+ });
343
+ });
344
+
345
+ it('calls metric correctly', async () => {
346
+ const metricName = 'sampleMetric';
347
+ fetch.mockResponseOnce(JSON.stringify({}));
348
+ await DataJunctionAPI.metric(metricName);
349
+ expect(fetch).toHaveBeenCalledWith(`${DJ_URL}/metrics/${metricName}/`, {
350
+ credentials: 'include',
351
+ });
352
+ });
353
+
354
+ it('calls metrics correctly', async () => {
355
+ fetch.mockResponseOnce(JSON.stringify({}));
356
+ await DataJunctionAPI.metrics('');
357
+ expect(fetch).toHaveBeenCalledWith(`${DJ_URL}/metrics/`, {
358
+ credentials: 'include',
359
+ });
360
+ });
361
+
362
+ it('calls listMetricMetadata correctly', async () => {
363
+ const nodeType = 'transform';
364
+ fetch.mockResponseOnce(JSON.stringify({}));
365
+ await DataJunctionAPI.listMetricMetadata();
366
+ expect(fetch).toHaveBeenCalledWith(`${DJ_URL}/metrics/metadata`, {
367
+ method: 'GET',
368
+ headers: { 'Content-Type': 'application/json' },
369
+ credentials: 'include',
370
+ });
371
+ });
372
+
373
+ it('calls namespaces correctly', async () => {
374
+ fetch.mockResponseOnce(JSON.stringify({}));
375
+ await DataJunctionAPI.namespaces();
376
+ expect(fetch).toHaveBeenCalledWith(`${DJ_URL}/namespaces/`, {
377
+ credentials: 'include',
378
+ });
379
+ });
380
+
381
+ it('calls clientCode correctly', async () => {
382
+ const name = 'sampleName';
383
+ fetch.mockResponseOnce(JSON.stringify({}));
384
+ await DataJunctionAPI.clientCode(name);
385
+ expect(fetch).toHaveBeenCalledWith(
386
+ `${DJ_URL}/datajunction-clients/python/new_node/${name}`,
387
+ {
388
+ credentials: 'include',
389
+ },
390
+ );
391
+ });
392
+
393
+ it('calls cube correctly', async () => {
394
+ const name = 'sampleCube';
395
+ fetch.mockResponseOnce(JSON.stringify({}));
396
+ await DataJunctionAPI.cube(name);
397
+ expect(fetch).toHaveBeenCalledWith(`${DJ_URL}/cubes/${name}/`, {
398
+ credentials: 'include',
399
+ });
400
+ });
401
+
402
+ it('calls commonDimensions correctly', async () => {
403
+ const metrics = ['metric1', 'metric2'];
404
+ const query = metrics.map(m => `metric=${m}`).join('&');
405
+ fetch.mockResponseOnce(JSON.stringify({}));
406
+ await DataJunctionAPI.commonDimensions(metrics);
407
+ expect(fetch).toHaveBeenCalledWith(
408
+ `${DJ_URL}/metrics/common/dimensions/?${query}`,
409
+ {
410
+ credentials: 'include',
411
+ },
412
+ );
413
+ });
414
+
415
+ it('calls history correctly', async () => {
416
+ const type = 'sampleType';
417
+ const name = 'sampleName';
418
+ const offset = 10;
419
+ const limit = 100;
420
+ fetch.mockResponseOnce(JSON.stringify({}));
421
+ await DataJunctionAPI.history(type, name, offset, limit);
422
+ expect(fetch).toHaveBeenCalledWith(
423
+ `${DJ_URL}/history?node=${name}&offset=${offset}&limit=${limit}`,
424
+ {
425
+ credentials: 'include',
426
+ },
427
+ );
428
+ });
429
+
430
+ it('calls revisions correctly', async () => {
431
+ const name = 'sampleName';
432
+ fetch.mockResponseOnce(JSON.stringify({}));
433
+ await DataJunctionAPI.revisions(name);
434
+ expect(fetch).toHaveBeenCalledWith(`${DJ_URL}/nodes/${name}/revisions/`, {
435
+ credentials: 'include',
436
+ });
437
+ });
438
+
439
+ it('calls namespace correctly', async () => {
440
+ const nmspce = 'sampleNamespace';
441
+ fetch.mockResponseOnce(JSON.stringify({}));
442
+ await DataJunctionAPI.namespace(nmspce);
443
+ expect(fetch).toHaveBeenCalledWith(
444
+ `${DJ_URL}/namespaces/${nmspce}?edited_by=undefined&with_edited_by=true`,
445
+ {
446
+ credentials: 'include',
447
+ },
448
+ );
449
+ });
450
+
451
+ it('calls sql correctly', async () => {
452
+ const metric_name = 'sampleMetric';
453
+ const selection = { key: 'value' };
454
+ const params = new URLSearchParams(selection).toString();
455
+ fetch.mockResponseOnce(JSON.stringify({}));
456
+ await DataJunctionAPI.sql(metric_name, selection);
457
+ expect(fetch).toHaveBeenCalledWith(
458
+ `${DJ_URL}/sql/${metric_name}?${params}`,
459
+ {
460
+ credentials: 'include',
461
+ },
462
+ );
463
+ });
464
+
465
+ it('calls nodesWithDimension correctly', async () => {
466
+ const name = 'sampleName';
467
+ fetch.mockResponseOnce(JSON.stringify({}));
468
+ await DataJunctionAPI.nodesWithDimension(name);
469
+ expect(fetch).toHaveBeenCalledWith(`${DJ_URL}/dimensions/${name}/nodes/`, {
470
+ credentials: 'include',
471
+ });
472
+ });
473
+
474
+ it('calls materializations correctly', async () => {
475
+ const nodeName = 'default.sample_node';
476
+ const mockMaterializations = [
477
+ { name: 'materialization1' },
478
+ { name: 'materialization2' },
479
+ ];
480
+
481
+ // Mock the first fetch call to return the list of materializations
482
+ fetch.mockResponseOnce(JSON.stringify(mockMaterializations));
483
+
484
+ // Mock the subsequent fetch calls to return clientCode for each materialization
485
+ fetch.mockResponses(
486
+ ...mockMaterializations.map(mat => [
487
+ JSON.stringify('from dj import DJClient'),
488
+ ]),
489
+ );
490
+
491
+ const result = await DataJunctionAPI.materializations(nodeName);
492
+
493
+ // Check the first fetch call
494
+ expect(fetch).toHaveBeenCalledWith(
495
+ `${DJ_URL}/nodes/${nodeName}/materializations?show_inactive=true&include_all_revisions=true`,
496
+ {
497
+ credentials: 'include',
498
+ },
499
+ );
500
+
501
+ // Ensure the result contains the clientCode for each materialization
502
+ expect(result).toEqual(mockMaterializations);
503
+ });
504
+
505
+ it('calls columns correctly', async () => {
506
+ const sampleNode = {
507
+ name: 'sampleNode',
508
+ columns: [
509
+ { name: 'column1', dimension: { name: 'dimension1' } },
510
+ { name: 'column2', dimension: null },
511
+ { name: 'column3', dimension: { name: 'dimension2' } },
512
+ ],
513
+ };
514
+
515
+ // Mock the fetch calls to return clientCode for columns with a dimension
516
+ const mockClientCodeResponses = sampleNode.columns
517
+ .filter(col => col.dimension)
518
+ .map(col => [JSON.stringify({ clientCode: col.clientCode })]);
519
+
520
+ fetch.mockResponses(...mockClientCodeResponses);
521
+
522
+ const result = await DataJunctionAPI.columns(sampleNode);
523
+ expect(result).toEqual(sampleNode.columns);
524
+ });
525
+
526
+ it('calls sqls correctly', async () => {
527
+ const metricSelection = ['metric1'];
528
+ const dimensionSelection = ['dimension1'];
529
+ const filters = 'sampleFilter';
530
+ const params = new URLSearchParams();
531
+ metricSelection.forEach(metric => params.append('metrics', metric));
532
+ dimensionSelection.forEach(dimension =>
533
+ params.append('dimensions', dimension),
534
+ );
535
+ params.append('filters', filters);
536
+ fetch.mockResponseOnce(JSON.stringify({}));
537
+ await DataJunctionAPI.sqls(metricSelection, dimensionSelection, filters);
538
+ expect(fetch).toHaveBeenCalledWith(`${DJ_URL}/sql/?${params}`, {
539
+ credentials: 'include',
540
+ });
541
+ });
542
+
543
+ it('calls data correctly', async () => {
544
+ const metricSelection = ['metric1'];
545
+ const dimensionSelection = ['dimension1'];
546
+ const params = new URLSearchParams();
547
+ metricSelection.forEach(metric => params.append('metrics', metric));
548
+ dimensionSelection.forEach(dimension =>
549
+ params.append('dimensions', dimension),
550
+ );
551
+ fetch.mockResponseOnce(JSON.stringify({}));
552
+ await DataJunctionAPI.data(metricSelection, dimensionSelection);
553
+ expect(fetch).toHaveBeenCalledWith(
554
+ `${DJ_URL}/data/?${params}&limit=10000`,
555
+ {
556
+ credentials: 'include',
557
+ },
558
+ );
559
+ });
560
+
561
+ it('calls compiledSql correctly', async () => {
562
+ const node = 'sampleNode';
563
+ fetch.mockResponseOnce(JSON.stringify({}));
564
+ await DataJunctionAPI.compiledSql(node);
565
+ expect(fetch).toHaveBeenCalledWith(`${DJ_URL}/sql/${node}/`, {
566
+ credentials: 'include',
567
+ });
568
+ });
569
+
570
+ it('transforms node data correctly', async () => {
571
+ fetch.mockResolvedValueOnce({
572
+ json: () =>
573
+ Promise.resolve({
574
+ message: undefined,
575
+ columns: [
576
+ {
577
+ name: 'id',
578
+ attributes: [
579
+ {
580
+ attribute_type: {
581
+ name: 'primary_key',
582
+ },
583
+ },
584
+ ],
585
+ },
586
+ ],
587
+ }),
588
+ status: 200,
589
+ });
590
+
591
+ const nodeData = await DataJunctionAPI.node('sampleNodeName');
592
+ expect(nodeData.primary_key).toEqual(['id']);
593
+ });
594
+
595
+ it('calls stream correctly', () => {
596
+ const metricSelection = ['metric1', 'metric2'];
597
+ const dimensionSelection = ['dimension1', 'dimension2'];
598
+ const filters = 'sampleFilter';
599
+
600
+ DataJunctionAPI.stream(metricSelection, dimensionSelection, filters);
601
+
602
+ const params = new URLSearchParams();
603
+ metricSelection.map(metric => params.append('metrics', metric));
604
+ dimensionSelection.map(dimension => params.append('dimensions', dimension));
605
+ params.append('filters', filters);
606
+
607
+ expect(global.EventSource).toHaveBeenCalledWith(
608
+ `${DJ_URL}/stream/?${params}&limit=10000&async_=true`,
609
+ { withCredentials: true },
610
+ );
611
+ });
612
+
613
+ it('calls streamNodeData correctly', () => {
614
+ const nodes = ['transform1'];
615
+ const dimensionSelection = ['dimension1', 'dimension2'];
616
+ const filters = 'sampleFilter';
617
+
618
+ DataJunctionAPI.streamNodeData(nodes[0], {
619
+ dimensions: dimensionSelection,
620
+ filters: filters,
621
+ });
622
+
623
+ const params = new URLSearchParams();
624
+ params.append('dimensions', dimensionSelection, 'filters', filters);
625
+
626
+ expect(global.EventSource).toHaveBeenCalledWith(
627
+ `${DJ_URL}/stream/transform1?filters=sampleFilter&dimensions=dimension1&dimensions=dimension2&limit=1000&async_=true`,
628
+ { withCredentials: true },
629
+ );
630
+ });
631
+
632
+ it('calls nodeData correctly', () => {
633
+ const nodes = ['transform1'];
634
+ const dimensionSelection = ['dimension1', 'dimension2'];
635
+ const filters = 'sampleFilter';
636
+ fetch.mockResponseOnce(JSON.stringify({}));
637
+
638
+ DataJunctionAPI.nodeData(nodes[0], {
639
+ dimensions: dimensionSelection,
640
+ filters: filters,
641
+ });
642
+
643
+ const params = new URLSearchParams();
644
+ params.append('dimensions', dimensionSelection, 'filters', filters);
645
+
646
+ expect(fetch).toHaveBeenCalledWith(
647
+ `${DJ_URL}/data/transform1?filters=sampleFilter&dimensions=dimension1&dimensions=dimension2&limit=1000&async_=true`,
648
+ {
649
+ credentials: 'include',
650
+ headers: {
651
+ 'Cache-Control': 'max-age=86400',
652
+ },
653
+ },
654
+ );
655
+ });
656
+
657
+ it('calls dag correctly and processes response', async () => {
658
+ const mockResponse = [
659
+ {
660
+ name: 'namespace.node1',
661
+ parents: [{ name: 'parent1' }, { name: null }],
662
+ columns: [
663
+ {
664
+ dimension: { name: 'dimension1' },
665
+ name: 'col1',
666
+ type: 'type1',
667
+ attributes: [],
668
+ },
669
+ {
670
+ dimension: null,
671
+ name: 'col2',
672
+ type: 'type2',
673
+ attributes: [{ attribute_type: { name: 'primary_key' } }],
674
+ },
675
+ ],
676
+ table: 'table1',
677
+ schema_: 'schema1',
678
+ display_name: 'DisplayName1',
679
+ type: 'type1',
680
+ },
681
+ ];
682
+
683
+ fetch.mockResponseOnce(JSON.stringify(mockResponse));
684
+ const result = await DataJunctionAPI.dag();
685
+
686
+ expect(fetch).toHaveBeenCalledWith(`${DJ_URL}/nodes/`, {
687
+ credentials: 'include',
688
+ });
689
+
690
+ expect(result.edges).toEqual([
691
+ {
692
+ id: 'namespace.node1-parent1',
693
+ target: 'namespace.node1',
694
+ source: 'parent1',
695
+ animated: true,
696
+ markerEnd: { type: 'arrow' },
697
+ },
698
+ {
699
+ id: 'namespace.node1-dimension1',
700
+ target: 'namespace.node1',
701
+ source: 'dimension1',
702
+ draggable: true,
703
+ },
704
+ ]);
705
+
706
+ expect(result.nodes).toEqual([
707
+ {
708
+ id: 'namespace.node1',
709
+ type: 'DJNode',
710
+ data: {
711
+ label: 'schema1.table1',
712
+ table: 'table1',
713
+ name: 'namespace.node1',
714
+ display_name: 'DisplayName1',
715
+ type: 'type1',
716
+ primary_key: ['col2'],
717
+ column_names: [
718
+ { name: 'col1', type: 'type1' },
719
+ { name: 'col2', type: 'type2' },
720
+ ],
721
+ },
722
+ },
723
+ ]);
724
+
725
+ expect(result.namespaces).toEqual([
726
+ {
727
+ id: 'namespace',
728
+ type: 'DJNamespace',
729
+ data: {
730
+ label: 'namespace',
731
+ },
732
+ },
733
+ ]);
734
+ });
735
+
736
+ it('calls link and unlink dimension correctly', async () => {
737
+ const nodeName = 'default.transform1';
738
+ const dimensionName = 'default.dimension1';
739
+ const columnName = 'column1';
740
+ fetch.mockResponseOnce(JSON.stringify({}));
741
+ await DataJunctionAPI.unlinkDimension(nodeName, columnName, dimensionName);
742
+ expect(fetch).toHaveBeenCalledWith(
743
+ `${DJ_URL}/nodes/${nodeName}/columns/${columnName}?dimension=${dimensionName}`,
744
+ {
745
+ credentials: 'include',
746
+ headers: {
747
+ 'Content-Type': 'application/json',
748
+ },
749
+ method: 'DELETE',
750
+ },
751
+ );
752
+
753
+ fetch.mockResponseOnce(JSON.stringify({}));
754
+ await DataJunctionAPI.linkDimension(nodeName, columnName, dimensionName);
755
+ expect(fetch).toHaveBeenCalledWith(
756
+ `${DJ_URL}/nodes/${nodeName}/columns/${columnName}?dimension=${dimensionName}`,
757
+ {
758
+ credentials: 'include',
759
+ headers: {
760
+ 'Content-Type': 'application/json',
761
+ },
762
+ method: 'POST',
763
+ },
764
+ );
765
+ });
766
+
767
+ it('calls add and remove complex dimension link correctly', async () => {
768
+ const nodeName = 'default.transform1';
769
+ const dimensionNode = 'default.dimension1';
770
+ const joinOn = 'blah';
771
+ fetch.mockResponseOnce(JSON.stringify({}));
772
+ await DataJunctionAPI.removeComplexDimensionLink(nodeName, dimensionNode);
773
+ expect(fetch).toHaveBeenCalledWith(`${DJ_URL}/nodes/${nodeName}/link`, {
774
+ credentials: 'include',
775
+ headers: {
776
+ 'Content-Type': 'application/json',
777
+ },
778
+ body: JSON.stringify({
779
+ dimension_node: dimensionNode,
780
+ role: null,
781
+ }),
782
+ method: 'DELETE',
783
+ });
784
+
785
+ fetch.mockResponseOnce(JSON.stringify({}));
786
+ await DataJunctionAPI.addComplexDimensionLink(
787
+ nodeName,
788
+ dimensionNode,
789
+ joinOn,
790
+ );
791
+ expect(fetch).toHaveBeenCalledWith(`${DJ_URL}/nodes/${nodeName}/link`, {
792
+ credentials: 'include',
793
+ headers: {
794
+ 'Content-Type': 'application/json',
795
+ },
796
+ body: JSON.stringify({
797
+ dimension_node: dimensionNode,
798
+ join_type: null,
799
+ join_on: joinOn,
800
+ join_cardinality: null,
801
+ role: null,
802
+ }),
803
+ method: 'POST',
804
+ });
805
+ });
806
+
807
+ it('calls deactivate correctly', async () => {
808
+ const nodeName = 'default.transform1';
809
+ fetch.mockResponseOnce(JSON.stringify({}));
810
+ await DataJunctionAPI.deactivate(nodeName);
811
+ expect(fetch).toHaveBeenCalledWith(`${DJ_URL}/nodes/${nodeName}`, {
812
+ credentials: 'include',
813
+ headers: {
814
+ 'Content-Type': 'application/json',
815
+ },
816
+ method: 'DELETE',
817
+ });
818
+ });
819
+
820
+ it('calls attributes correctly', async () => {
821
+ fetch.mockResponseOnce(JSON.stringify(mocks.attributes));
822
+ await DataJunctionAPI.attributes();
823
+ expect(fetch).toHaveBeenCalledWith(`${DJ_URL}/attributes`, {
824
+ credentials: 'include',
825
+ });
826
+ });
827
+
828
+ it('calls dimensions correctly', async () => {
829
+ fetch.mockResponseOnce(JSON.stringify(mocks.dimensions));
830
+ await DataJunctionAPI.dimensions();
831
+ expect(fetch).toHaveBeenCalledWith(`${DJ_URL}/dimensions`, {
832
+ credentials: 'include',
833
+ });
834
+ });
835
+
836
+ it('calls setAttributes correctly', async () => {
837
+ const nodeName = 'default.transform1';
838
+ const attributes = ['system'];
839
+ const columnName = 'column1';
840
+ fetch.mockResponseOnce(JSON.stringify({}));
841
+ await DataJunctionAPI.setAttributes(nodeName, columnName, attributes);
842
+ expect(fetch).toHaveBeenCalledWith(
843
+ `${DJ_URL}/nodes/${nodeName}/columns/${columnName}/attributes`,
844
+ {
845
+ credentials: 'include',
846
+ body: JSON.stringify([{ namespace: 'system', name: 'system' }]),
847
+ headers: {
848
+ 'Content-Type': 'application/json',
849
+ },
850
+ method: 'POST',
851
+ },
852
+ );
853
+ });
854
+
855
+ it('calls addNamespace correctly', async () => {
856
+ fetch.mockResponseOnce(JSON.stringify({}));
857
+ await DataJunctionAPI.addNamespace('test');
858
+ expect(fetch).toHaveBeenCalledWith(`${DJ_URL}/namespaces/test`, {
859
+ credentials: 'include',
860
+ headers: {
861
+ 'Content-Type': 'application/json',
862
+ },
863
+ method: 'POST',
864
+ });
865
+ });
866
+
867
+ it('calls listTags correctly', async () => {
868
+ fetch.mockResponseOnce(JSON.stringify(mocks.tags));
869
+ const res = await DataJunctionAPI.listTags();
870
+ expect(fetch).toHaveBeenCalledWith(`${DJ_URL}/tags`, {
871
+ credentials: 'include',
872
+ headers: {
873
+ 'Content-Type': 'application/json',
874
+ },
875
+ method: 'GET',
876
+ });
877
+ expect(res).toEqual(mocks.tags);
878
+ });
879
+
880
+ it('calls getTag correctly', async () => {
881
+ fetch.mockResponseOnce(JSON.stringify(mocks.tags[0]));
882
+ const res = await DataJunctionAPI.getTag('report.financials');
883
+ expect(fetch).toHaveBeenCalledWith(`${DJ_URL}/tags/report.financials`, {
884
+ credentials: 'include',
885
+ headers: {
886
+ 'Content-Type': 'application/json',
887
+ },
888
+ method: 'GET',
889
+ });
890
+ expect(res).toEqual(mocks.tags[0]);
891
+ });
892
+
893
+ it('calls listNodesForTag correctly', async () => {
894
+ fetch.mockResponseOnce(JSON.stringify(mocks.mockLinkedNodes));
895
+ const res = await DataJunctionAPI.listNodesForTag('report.financials');
896
+ expect(fetch).toHaveBeenCalledWith(
897
+ `${DJ_URL}/tags/report.financials/nodes`,
898
+ {
899
+ credentials: 'include',
900
+ headers: {
901
+ 'Content-Type': 'application/json',
902
+ },
903
+ method: 'GET',
904
+ },
905
+ );
906
+ expect(res).toEqual(mocks.mockLinkedNodes);
907
+ });
908
+
909
+ it('calls tagsNode correctly', async () => {
910
+ fetch.mockResponseOnce(JSON.stringify(mocks.mockLinkedNodes));
911
+ await DataJunctionAPI.tagsNode('default.num_repair_orders', [
912
+ 'report.financials',
913
+ 'report.forecasts',
914
+ ]);
915
+ expect(fetch).toHaveBeenCalledWith(
916
+ `${DJ_URL}/nodes/default.num_repair_orders/tags?tag_names=report.financials&tag_names=report.forecasts`,
917
+ {
918
+ credentials: 'include',
919
+ headers: {
920
+ 'Content-Type': 'application/json',
921
+ },
922
+ method: 'POST',
923
+ },
924
+ );
925
+ });
926
+
927
+ it('calls addTag correctly', async () => {
928
+ fetch.mockResponseOnce(JSON.stringify(mocks.mockLinkedNodes));
929
+ await DataJunctionAPI.addTag(
930
+ 'report.financials',
931
+ 'Financial Reports',
932
+ 'report',
933
+ 'financial reports',
934
+ );
935
+ expect(fetch).toHaveBeenCalledWith(`${DJ_URL}/tags`, {
936
+ credentials: 'include',
937
+ headers: {
938
+ 'Content-Type': 'application/json',
939
+ },
940
+ body: JSON.stringify({
941
+ name: 'report.financials',
942
+ display_name: 'Financial Reports',
943
+ tag_type: 'report',
944
+ description: 'financial reports',
945
+ }),
946
+ method: 'POST',
947
+ });
948
+ });
949
+
950
+ it('calls editTag correctly', async () => {
951
+ fetch.mockResponseOnce(JSON.stringify({}));
952
+ await DataJunctionAPI.editTag(
953
+ 'report.financials',
954
+ 'Financial reports',
955
+ 'Financial Reports',
956
+ );
957
+ expect(fetch).toHaveBeenCalledWith(`${DJ_URL}/tags/report.financials`, {
958
+ credentials: 'include',
959
+ headers: {
960
+ 'Content-Type': 'application/json',
961
+ },
962
+ body: JSON.stringify({
963
+ description: 'Financial reports',
964
+ display_name: 'Financial Reports',
965
+ }),
966
+ method: 'PATCH',
967
+ });
968
+ });
969
+
970
+ it('calls setPartition correctly', async () => {
971
+ fetch.mockResponseOnce(JSON.stringify({}));
972
+ await DataJunctionAPI.setPartition(
973
+ 'default.hard_hat',
974
+ 'hire_date',
975
+ 'temporal',
976
+ 'yyyyMMdd',
977
+ 'day',
978
+ );
979
+ expect(fetch).toHaveBeenCalledWith(
980
+ `${DJ_URL}/nodes/default.hard_hat/columns/hire_date/partition`,
981
+ {
982
+ credentials: 'include',
983
+ headers: {
984
+ 'Content-Type': 'application/json',
985
+ },
986
+ body: JSON.stringify({
987
+ type_: 'temporal',
988
+ format: 'yyyyMMdd',
989
+ granularity: 'day',
990
+ }),
991
+ method: 'POST',
992
+ },
993
+ );
994
+ });
995
+
996
+ it('calls materialize correctly', async () => {
997
+ fetch.mockResponseOnce(JSON.stringify({}));
998
+ await DataJunctionAPI.materialize(
999
+ 'default.hard_hat',
1000
+ 'spark_sql',
1001
+ 'full',
1002
+ '@daily',
1003
+ {},
1004
+ );
1005
+ expect(fetch).toHaveBeenCalledWith(
1006
+ `${DJ_URL}/nodes/default.hard_hat/materialization`,
1007
+ {
1008
+ credentials: 'include',
1009
+ headers: {
1010
+ 'Content-Type': 'application/json',
1011
+ },
1012
+ body: JSON.stringify({
1013
+ job: 'spark_sql',
1014
+ strategy: 'full',
1015
+ schedule: '@daily',
1016
+ config: {},
1017
+ }),
1018
+ method: 'POST',
1019
+ },
1020
+ );
1021
+ });
1022
+
1023
+ it('calls runBackfill correctly', async () => {
1024
+ fetch.mockResponseOnce(JSON.stringify({}));
1025
+ await DataJunctionAPI.runBackfill('default.hard_hat', 'spark', [
1026
+ {
1027
+ columnName: 'hire_date',
1028
+ range: ['20230101', '20230202'],
1029
+ },
1030
+ ]);
1031
+ expect(fetch).toHaveBeenCalledWith(
1032
+ `${DJ_URL}/nodes/default.hard_hat/materializations/spark/backfill`,
1033
+ {
1034
+ credentials: 'include',
1035
+ headers: {
1036
+ 'Content-Type': 'application/json',
1037
+ },
1038
+ body: JSON.stringify([
1039
+ {
1040
+ column_name: 'hire_date',
1041
+ range: ['20230101', '20230202'],
1042
+ },
1043
+ ]),
1044
+ method: 'POST',
1045
+ },
1046
+ );
1047
+ });
1048
+
1049
+ it('calls materializationInfo correctly', async () => {
1050
+ fetch.mockResponseOnce(JSON.stringify({}));
1051
+ await DataJunctionAPI.materializationInfo();
1052
+ expect(fetch).toHaveBeenCalledWith(`${DJ_URL}/materialization/info`, {
1053
+ credentials: 'include',
1054
+ });
1055
+ });
1056
+
1057
+ it('calls revalidate correctly', async () => {
1058
+ fetch.mockResponseOnce(JSON.stringify({}));
1059
+ await DataJunctionAPI.revalidate('default.hard_hat');
1060
+ expect(fetch).toHaveBeenCalledWith(
1061
+ `${DJ_URL}/nodes/default.hard_hat/validate`,
1062
+ {
1063
+ credentials: 'include',
1064
+ method: 'POST',
1065
+ headers: {
1066
+ 'Content-Type': 'application/json',
1067
+ },
1068
+ },
1069
+ );
1070
+ });
1071
+
1072
+ it('calls deleteMaterialization correctly', () => {
1073
+ const nodeName = 'transform1';
1074
+ const materializationName = 'sampleMaterialization';
1075
+ fetch.mockResponseOnce(JSON.stringify({}));
1076
+
1077
+ DataJunctionAPI.deleteMaterialization(nodeName, materializationName);
1078
+ expect(fetch).toHaveBeenCalledWith(
1079
+ `${DJ_URL}/nodes/${nodeName}/materializations?materialization_name=${materializationName}`,
1080
+ {
1081
+ method: 'DELETE',
1082
+ credentials: 'include',
1083
+ headers: {
1084
+ 'Content-Type': 'application/json',
1085
+ },
1086
+ },
1087
+ );
1088
+ });
1089
+
1090
+ it('calls listNodesForLanding correctly', () => {
1091
+ fetch.mockResponseOnce(JSON.stringify({}));
1092
+
1093
+ DataJunctionAPI.listNodesForLanding(
1094
+ '',
1095
+ ['source'],
1096
+ [],
1097
+ '',
1098
+ null,
1099
+ null,
1100
+ 100,
1101
+ {
1102
+ key: 'updatedAt',
1103
+ direction: 'descending',
1104
+ },
1105
+ );
1106
+ expect(fetch).toHaveBeenCalledWith(
1107
+ `${DJ_URL}/graphql`,
1108
+ expect.objectContaining({
1109
+ method: 'POST',
1110
+ credentials: 'include',
1111
+ headers: {
1112
+ 'Content-Type': 'application/json',
1113
+ },
1114
+ }),
1115
+ );
1116
+ });
1117
+
1118
+ it('calls getMetric correctly', async () => {
1119
+ fetch.mockResponseOnce(
1120
+ JSON.stringify({
1121
+ data: { findNodes: [{ name: 'default.num_repair_orders' }] },
1122
+ }),
1123
+ );
1124
+ await DataJunctionAPI.getMetric('default.num_repair_orders');
1125
+ expect(fetch).toHaveBeenCalledWith(
1126
+ `${DJ_URL}/graphql`,
1127
+ expect.objectContaining({
1128
+ method: 'POST',
1129
+ credentials: 'include',
1130
+ headers: {
1131
+ 'Content-Type': 'application/json',
1132
+ },
1133
+ }),
1134
+ );
1135
+ });
1136
+
1137
+ it('calls notebookExportCube correctly', async () => {
1138
+ fetch.mockResponseOnce(JSON.stringify({}));
1139
+ await DataJunctionAPI.notebookExportCube('default.repairs_cube');
1140
+ expect(fetch).toHaveBeenCalledWith(
1141
+ `${DJ_URL}/datajunction-clients/python/notebook/?cube=default.repairs_cube`,
1142
+ {
1143
+ credentials: 'include',
1144
+ },
1145
+ );
1146
+ });
1147
+
1148
+ it('calls notebookExportNamespace correctly', async () => {
1149
+ fetch.mockResponseOnce(JSON.stringify({}));
1150
+ await DataJunctionAPI.notebookExportNamespace('default');
1151
+ expect(fetch).toHaveBeenCalledWith(
1152
+ `${DJ_URL}/datajunction-clients/python/notebook/?namespace=default`,
1153
+ {
1154
+ credentials: 'include',
1155
+ },
1156
+ );
1157
+ });
1158
+
1159
+ it('calls node_counts_by_type correctly', async () => {
1160
+ fetch.mockResponseOnce(
1161
+ JSON.stringify([
1162
+ [
1163
+ {
1164
+ value: 'cube',
1165
+ col: 'system.dj.node_type.type',
1166
+ },
1167
+ {
1168
+ value: 226,
1169
+ col: 'system.dj.number_of_nodes',
1170
+ },
1171
+ ],
1172
+ [
1173
+ {
1174
+ value: 'dimension',
1175
+ col: 'system.dj.node_type.type',
1176
+ },
1177
+ {
1178
+ value: 241,
1179
+ col: 'system.dj.number_of_nodes',
1180
+ },
1181
+ ],
1182
+ [
1183
+ {
1184
+ value: 'metric',
1185
+ col: 'system.dj.node_type.type',
1186
+ },
1187
+ {
1188
+ value: 2853,
1189
+ col: 'system.dj.number_of_nodes',
1190
+ },
1191
+ ],
1192
+ [
1193
+ {
1194
+ value: 'source',
1195
+ col: 'system.dj.node_type.type',
1196
+ },
1197
+ {
1198
+ value: 540,
1199
+ col: 'system.dj.number_of_nodes',
1200
+ },
1201
+ ],
1202
+ [
1203
+ {
1204
+ value: 'transform',
1205
+ col: 'system.dj.node_type.type',
1206
+ },
1207
+ {
1208
+ value: 663,
1209
+ col: 'system.dj.number_of_nodes',
1210
+ },
1211
+ ],
1212
+ ]),
1213
+ );
1214
+ const results = await DataJunctionAPI.system.node_counts_by_type();
1215
+ expect(fetch).toHaveBeenCalledWith(
1216
+ `${DJ_URL}/system/data/system.dj.number_of_nodes?dimensions=system.dj.node_type.type&filters=system.dj.is_active.active_id%3Dtrue&orderby=system.dj.node_type.type`,
1217
+ {
1218
+ credentials: 'include',
1219
+ },
1220
+ );
1221
+ expect(results).toEqual([
1222
+ { name: 'cube', value: 226 },
1223
+ { name: 'dimension', value: 241 },
1224
+ { name: 'metric', value: 2853 },
1225
+ { name: 'source', value: 540 },
1226
+ { name: 'transform', value: 663 },
1227
+ ]);
1228
+ });
1229
+
1230
+ it('calls node_counts_by_active correctly', async () => {
1231
+ fetch.mockResponseOnce(
1232
+ JSON.stringify([
1233
+ [
1234
+ {
1235
+ value: false,
1236
+ col: 'system.dj.is_active.active_id',
1237
+ },
1238
+ {
1239
+ value: 3136,
1240
+ col: 'system.dj.number_of_nodes',
1241
+ },
1242
+ ],
1243
+ [
1244
+ {
1245
+ value: true,
1246
+ col: 'system.dj.is_active.active_id',
1247
+ },
1248
+ {
1249
+ value: 4523,
1250
+ col: 'system.dj.number_of_nodes',
1251
+ },
1252
+ ],
1253
+ ]),
1254
+ );
1255
+ const results = await DataJunctionAPI.system.node_counts_by_active();
1256
+ expect(fetch).toHaveBeenCalledWith(
1257
+ `${DJ_URL}/system/data/system.dj.number_of_nodes?dimensions=system.dj.is_active.active_id`,
1258
+ {
1259
+ credentials: 'include',
1260
+ },
1261
+ );
1262
+ expect(results).toEqual([
1263
+ { name: 'false', value: 3136 },
1264
+ { name: 'true', value: 4523 },
1265
+ ]);
1266
+ });
1267
+
1268
+ it('calls node_counts_by_status correctly', async () => {
1269
+ fetch.mockResponseOnce(
1270
+ JSON.stringify([
1271
+ [
1272
+ {
1273
+ value: 'VALID',
1274
+ col: 'system.dj.nodes.status',
1275
+ },
1276
+ {
1277
+ value: 4333,
1278
+ col: 'system.dj.number_of_nodes',
1279
+ },
1280
+ ],
1281
+ [
1282
+ {
1283
+ value: 'INVALID',
1284
+ col: 'system.dj.nodes.status',
1285
+ },
1286
+ {
1287
+ value: 190,
1288
+ col: 'system.dj.number_of_nodes',
1289
+ },
1290
+ ],
1291
+ ]),
1292
+ );
1293
+ const results = await DataJunctionAPI.system.node_counts_by_status();
1294
+ expect(fetch).toHaveBeenCalledWith(
1295
+ `${DJ_URL}/system/data/system.dj.number_of_nodes?dimensions=system.dj.nodes.status&filters=system.dj.is_active.active_id%3Dtrue&orderby=system.dj.nodes.status`,
1296
+ {
1297
+ credentials: 'include',
1298
+ },
1299
+ );
1300
+ expect(results).toEqual([
1301
+ { name: 'VALID', value: 4333 },
1302
+ { name: 'INVALID', value: 190 },
1303
+ ]);
1304
+ });
1305
+
1306
+ it('calls nodes_without_description correctly', async () => {
1307
+ fetch.mockResponseOnce(
1308
+ JSON.stringify([
1309
+ [
1310
+ {
1311
+ value: 'cube',
1312
+ col: 'system.dj.node_type.type',
1313
+ },
1314
+ {
1315
+ value: 0.1,
1316
+ col: 'system.dj.node_without_description',
1317
+ },
1318
+ ],
1319
+ [
1320
+ {
1321
+ value: 'dimension',
1322
+ col: 'system.dj.node_type.type',
1323
+ },
1324
+ {
1325
+ value: 0.2,
1326
+ col: 'system.dj.node_without_description',
1327
+ },
1328
+ ],
1329
+ [
1330
+ {
1331
+ value: 'metric',
1332
+ col: 'system.dj.node_type.type',
1333
+ },
1334
+ {
1335
+ value: 0.3,
1336
+ col: 'system.dj.node_without_description',
1337
+ },
1338
+ ],
1339
+ [
1340
+ {
1341
+ value: 'source',
1342
+ col: 'system.dj.node_type.type',
1343
+ },
1344
+ {
1345
+ value: 0.4,
1346
+ col: 'system.dj.node_without_description',
1347
+ },
1348
+ ],
1349
+ [
1350
+ {
1351
+ value: 'transform',
1352
+ col: 'system.dj.node_type.type',
1353
+ },
1354
+ {
1355
+ value: 0.5,
1356
+ col: 'system.dj.node_without_description',
1357
+ },
1358
+ ],
1359
+ ]),
1360
+ );
1361
+ const results = await DataJunctionAPI.system.nodes_without_description();
1362
+ expect(fetch).toHaveBeenCalledWith(
1363
+ `${DJ_URL}/system/data/system.dj.node_without_description?dimensions=system.dj.node_type.type&filters=system.dj.is_active.active_id%3Dtrue&orderby=system.dj.node_type.type`,
1364
+ {
1365
+ credentials: 'include',
1366
+ },
1367
+ );
1368
+ expect(results).toEqual([
1369
+ { name: 'cube', value: 0.1 },
1370
+ { name: 'dimension', value: 0.2 },
1371
+ { name: 'metric', value: 0.3 },
1372
+ { name: 'source', value: 0.4 },
1373
+ { name: 'transform', value: 0.5 },
1374
+ ]);
1375
+ });
1376
+ it('calls node_trends correctly', async () => {
1377
+ fetch.mockResponseOnce(
1378
+ JSON.stringify([
1379
+ [
1380
+ {
1381
+ value: 20250630,
1382
+ col: 'system.dj.nodes.created_at_week',
1383
+ },
1384
+ {
1385
+ value: 'metric',
1386
+ col: 'system.dj.node_type.type',
1387
+ },
1388
+ {
1389
+ value: 42,
1390
+ col: 'system.dj.number_of_nodes',
1391
+ },
1392
+ ],
1393
+ [
1394
+ {
1395
+ value: 20250707,
1396
+ col: 'system.dj.nodes.created_at_week',
1397
+ },
1398
+ {
1399
+ value: 'dimension',
1400
+ col: 'system.dj.node_type.type',
1401
+ },
1402
+ {
1403
+ value: 21,
1404
+ col: 'system.dj.number_of_nodes',
1405
+ },
1406
+ ],
1407
+ [
1408
+ {
1409
+ value: 20250707,
1410
+ col: 'system.dj.nodes.created_at_week',
1411
+ },
1412
+ {
1413
+ value: 'metric',
1414
+ col: 'system.dj.node_type.type',
1415
+ },
1416
+ {
1417
+ value: 9,
1418
+ col: 'system.dj.number_of_nodes',
1419
+ },
1420
+ ],
1421
+ [
1422
+ {
1423
+ value: 20250714,
1424
+ col: 'system.dj.nodes.created_at_week',
1425
+ },
1426
+ {
1427
+ value: 'metric',
1428
+ col: 'system.dj.node_type.type',
1429
+ },
1430
+ {
1431
+ value: 3,
1432
+ col: 'system.dj.number_of_nodes',
1433
+ },
1434
+ ],
1435
+ [
1436
+ {
1437
+ value: 20250714,
1438
+ col: 'system.dj.nodes.created_at_week',
1439
+ },
1440
+ {
1441
+ value: 'dimension',
1442
+ col: 'system.dj.node_type.type',
1443
+ },
1444
+ {
1445
+ value: 7,
1446
+ col: 'system.dj.number_of_nodes',
1447
+ },
1448
+ ],
1449
+ ]),
1450
+ );
1451
+ const results = await DataJunctionAPI.system.node_trends();
1452
+ expect(fetch).toHaveBeenCalledWith(
1453
+ `${DJ_URL}/system/data/system.dj.number_of_nodes?dimensions=system.dj.nodes.created_at_week&dimensions=system.dj.node_type.type&filters=system.dj.nodes.created_at_week>=20240101&orderby=system.dj.nodes.created_at_week`,
1454
+ {
1455
+ credentials: 'include',
1456
+ },
1457
+ );
1458
+ expect(results).toEqual([
1459
+ { date: 20250630, metric: 42 },
1460
+ { date: 20250707, dimension: 21, metric: 9 },
1461
+ { date: 20250714, dimension: 7, metric: 3 },
1462
+ ]);
1463
+ });
1464
+
1465
+ it('calls materialization_counts_by_type correctly', async () => {
1466
+ fetch.mockResponseOnce(
1467
+ JSON.stringify([
1468
+ [
1469
+ {
1470
+ value: 'cube',
1471
+ col: 'system.dj.node_type.type',
1472
+ },
1473
+ {
1474
+ value: 76,
1475
+ col: 'system.dj.number_of_materializations',
1476
+ },
1477
+ ],
1478
+ [
1479
+ {
1480
+ value: 'dimension',
1481
+ col: 'system.dj.node_type.type',
1482
+ },
1483
+ {
1484
+ value: 3,
1485
+ col: 'system.dj.number_of_materializations',
1486
+ },
1487
+ ],
1488
+ [
1489
+ {
1490
+ value: 'transform',
1491
+ col: 'system.dj.node_type.type',
1492
+ },
1493
+ {
1494
+ value: 9,
1495
+ col: 'system.dj.number_of_materializations',
1496
+ },
1497
+ ],
1498
+ ]),
1499
+ );
1500
+ const results =
1501
+ await DataJunctionAPI.system.materialization_counts_by_type();
1502
+ expect(fetch).toHaveBeenCalledWith(
1503
+ `${DJ_URL}/system/data/system.dj.number_of_materializations?dimensions=system.dj.node_type.type&filters=system.dj.is_active.active_id%3Dtrue&orderby=system.dj.node_type.type`,
1504
+ {
1505
+ credentials: 'include',
1506
+ },
1507
+ );
1508
+ expect(results).toEqual([
1509
+ { name: 'cube', value: 76 },
1510
+ { name: 'dimension', value: 3 },
1511
+ { name: 'transform', value: 9 },
1512
+ ]);
1513
+ });
1514
+
1515
+ it('calls system.dimensions correctly', async () => {
1516
+ fetch.mockResponseOnce(JSON.stringify([]));
1517
+ const results = await DataJunctionAPI.system.dimensions();
1518
+ expect(fetch).toHaveBeenCalledWith(`${DJ_URL}/system/dimensions`, {
1519
+ credentials: 'include',
1520
+ });
1521
+ });
1522
+
1523
+ it('calls availabilityStates correctly', async () => {
1524
+ const nodeName = 'default.sample_node';
1525
+ const mockAvailabilityStates = [
1526
+ {
1527
+ id: 1,
1528
+ catalog: 'test_catalog',
1529
+ schema_: 'test_schema',
1530
+ table: 'test_table',
1531
+ valid_through_ts: 1640995200,
1532
+ url: 'http://example.com/table',
1533
+ node_revision_id: 123,
1534
+ node_version: '1.0.0',
1535
+ },
1536
+ ];
1537
+
1538
+ fetch.mockResponseOnce(JSON.stringify(mockAvailabilityStates));
1539
+
1540
+ const result = await DataJunctionAPI.availabilityStates(nodeName);
1541
+
1542
+ expect(fetch).toHaveBeenCalledWith(
1543
+ `${DJ_URL}/nodes/${nodeName}/availability/`,
1544
+ {
1545
+ credentials: 'include',
1546
+ },
1547
+ );
1548
+ expect(result).toEqual(mockAvailabilityStates);
1549
+ });
1550
+
1551
+ it('calls refreshLatestMaterialization correctly', async () => {
1552
+ const nodeName = 'default.sample_cube';
1553
+ const mockResponse = { message: 'Materialization refreshed successfully' };
1554
+
1555
+ fetch.mockResponseOnce(JSON.stringify(mockResponse));
1556
+
1557
+ const result = await DataJunctionAPI.refreshLatestMaterialization(nodeName);
1558
+
1559
+ expect(fetch).toHaveBeenCalledWith(
1560
+ `${DJ_URL}/nodes/${nodeName}?refresh_materialization=true`,
1561
+ {
1562
+ method: 'PATCH',
1563
+ headers: {
1564
+ 'Content-Type': 'application/json',
1565
+ },
1566
+ body: JSON.stringify({}),
1567
+ credentials: 'include',
1568
+ },
1569
+ );
1570
+ expect(result).toEqual({ status: 200, json: mockResponse });
1571
+ });
1572
+
1573
+ // Reference dimension links
1574
+ it('calls addReferenceDimensionLink correctly', async () => {
1575
+ const mockResponse = { message: 'Success' };
1576
+ fetch.mockResponseOnce(JSON.stringify(mockResponse));
1577
+ const result = await DataJunctionAPI.addReferenceDimensionLink(
1578
+ 'default.node1',
1579
+ 'column1',
1580
+ 'default.dimension1',
1581
+ 'dimension_col',
1582
+ );
1583
+ expect(fetch).toHaveBeenCalledWith(
1584
+ `${DJ_URL}/nodes/default.node1/columns/column1/link?dimension_node=default.dimension1&dimension_column=dimension_col`,
1585
+ expect.objectContaining({
1586
+ method: 'POST',
1587
+ headers: {
1588
+ 'Content-Type': 'application/json',
1589
+ },
1590
+ credentials: 'include',
1591
+ }),
1592
+ );
1593
+ expect(result).toEqual({ status: 200, json: mockResponse });
1594
+ });
1595
+
1596
+ it('calls removeReferenceDimensionLink correctly', async () => {
1597
+ fetch.mockResponseOnce(JSON.stringify({ message: 'Success' }));
1598
+ const result = await DataJunctionAPI.removeReferenceDimensionLink(
1599
+ 'default.node1',
1600
+ 'column1',
1601
+ );
1602
+ expect(fetch).toHaveBeenCalledWith(
1603
+ `${DJ_URL}/nodes/default.node1/columns/column1/link`,
1604
+ expect.objectContaining({
1605
+ method: 'DELETE',
1606
+ }),
1607
+ );
1608
+ });
1609
+
1610
+ // Materialization
1611
+ it('calls deleteMaterialization with nodeVersion correctly', async () => {
1612
+ fetch.mockResponseOnce(JSON.stringify({ message: 'Success' }));
1613
+ await DataJunctionAPI.deleteMaterialization(
1614
+ 'default.node1',
1615
+ 'mat1',
1616
+ 'v1.0',
1617
+ );
1618
+ expect(fetch).toHaveBeenCalledWith(
1619
+ 'http://localhost:8000/nodes/default.node1/materializations?materialization_name=mat1&node_version=v1.0',
1620
+ expect.objectContaining({ method: 'DELETE' }),
1621
+ );
1622
+ });
1623
+
1624
+ it('calls deleteMaterialization without nodeVersion correctly', async () => {
1625
+ fetch.mockResponseOnce(JSON.stringify({ message: 'Success' }));
1626
+ await DataJunctionAPI.deleteMaterialization('default.node1', 'mat1');
1627
+ expect(fetch).toHaveBeenCalledWith(
1628
+ 'http://localhost:8000/nodes/default.node1/materializations?materialization_name=mat1',
1629
+ expect.objectContaining({ method: 'DELETE' }),
1630
+ );
1631
+ });
1632
+
1633
+ // Notifications
1634
+ it('calls getNotificationPreferences correctly', async () => {
1635
+ fetch.mockResponseOnce(JSON.stringify([]));
1636
+ await DataJunctionAPI.getNotificationPreferences({ entity_type: 'node' });
1637
+ expect(fetch).toHaveBeenCalledWith(
1638
+ 'http://localhost:8000/notifications/?entity_type=node',
1639
+ expect.objectContaining({ credentials: 'include' }),
1640
+ );
1641
+ });
1642
+
1643
+ it('calls subscribeToNotifications correctly', async () => {
1644
+ fetch.mockResponseOnce(JSON.stringify({ status: 'subscribed' }));
1645
+ await DataJunctionAPI.subscribeToNotifications({
1646
+ entity_type: 'node',
1647
+ entity_name: 'default.node1',
1648
+ activity_types: ['create'],
1649
+ alert_types: ['email'],
1650
+ });
1651
+ expect(fetch).toHaveBeenCalledWith(
1652
+ 'http://localhost:8000/notifications/subscribe',
1653
+ expect.objectContaining({
1654
+ method: 'POST',
1655
+ body: expect.any(String),
1656
+ }),
1657
+ );
1658
+ });
1659
+
1660
+ it('calls unsubscribeFromNotifications correctly', async () => {
1661
+ fetch.mockResponseOnce(JSON.stringify({ status: 'unsubscribed' }));
1662
+ await DataJunctionAPI.unsubscribeFromNotifications({
1663
+ entity_type: 'node',
1664
+ entity_name: 'default.node1',
1665
+ });
1666
+ expect(fetch).toHaveBeenCalledWith(
1667
+ expect.stringContaining('/notifications/unsubscribe'),
1668
+ expect.objectContaining({ method: 'DELETE' }),
1669
+ );
1670
+ });
1671
+
1672
+ // materializeCube (lines 1323-1339)
1673
+ it('calls materializeCube correctly', async () => {
1674
+ fetch.mockResponseOnce(JSON.stringify({ message: 'Success' }));
1675
+ const result = await DataJunctionAPI.materializeCube(
1676
+ 'default.cube1',
1677
+ 'spark_sql',
1678
+ 'incremental_time',
1679
+ '0 0 * * *',
1680
+ '1 day',
1681
+ );
1682
+ expect(fetch).toHaveBeenCalledWith(
1683
+ 'http://localhost:8000/nodes/default.cube1/materialization',
1684
+ expect.objectContaining({
1685
+ method: 'POST',
1686
+ body: expect.stringContaining('lookback_window'),
1687
+ }),
1688
+ );
1689
+ expect(result).toEqual({ status: 200, json: { message: 'Success' } });
1690
+ });
1691
+
1692
+ // runBackfill
1693
+ it('calls runBackfill correctly', async () => {
1694
+ fetch.mockResponseOnce(JSON.stringify({ message: 'Success' }));
1695
+ const result = await DataJunctionAPI.runBackfill('default.node1', 'mat1', [
1696
+ {
1697
+ columnName: 'date',
1698
+ range: ['2023-01-01', '2023-12-31'],
1699
+ values: [],
1700
+ },
1701
+ ]);
1702
+ expect(fetch).toHaveBeenCalledWith(
1703
+ 'http://localhost:8000/nodes/default.node1/materializations/mat1/backfill',
1704
+ expect.objectContaining({
1705
+ method: 'POST',
1706
+ body: expect.stringContaining('column_name'),
1707
+ }),
1708
+ );
1709
+ expect(result).toEqual({ status: 200, json: { message: 'Success' } });
1710
+ });
1711
+
1712
+ // addReferenceDimensionLink with role parameter
1713
+ it('calls addReferenceDimensionLink with role correctly', async () => {
1714
+ const mockResponse = { message: 'Success' };
1715
+ fetch.mockResponseOnce(JSON.stringify(mockResponse));
1716
+ const result = await DataJunctionAPI.addReferenceDimensionLink(
1717
+ 'default.node1',
1718
+ 'column1',
1719
+ 'default.dimension1',
1720
+ 'dimension_col',
1721
+ 'birth_date',
1722
+ );
1723
+ expect(fetch).toHaveBeenCalledWith(
1724
+ expect.stringContaining('role=birth_date'),
1725
+ expect.objectContaining({
1726
+ method: 'POST',
1727
+ }),
1728
+ );
1729
+ expect(result).toEqual({ status: 200, json: mockResponse });
1730
+ });
1731
+
1732
+ // Test getNotificationPreferences without params
1733
+ it('calls getNotificationPreferences without params correctly', async () => {
1734
+ fetch.mockResponseOnce(JSON.stringify([]));
1735
+ await DataJunctionAPI.getNotificationPreferences();
1736
+ expect(fetch).toHaveBeenCalledWith(
1737
+ 'http://localhost:8000/notifications/',
1738
+ expect.objectContaining({ credentials: 'include' }),
1739
+ );
1740
+ });
1741
+
1742
+ // Test listMetricMetadata
1743
+ it('calls listMetricMetadata correctly', async () => {
1744
+ fetch.mockResponseOnce(JSON.stringify([{ name: 'metric1' }]));
1745
+ const result = await DataJunctionAPI.listMetricMetadata();
1746
+ expect(fetch).toHaveBeenCalledWith(
1747
+ 'http://localhost:8000/metrics/metadata',
1748
+ expect.objectContaining({
1749
+ method: 'GET',
1750
+ headers: {
1751
+ 'Content-Type': 'application/json',
1752
+ },
1753
+ }),
1754
+ );
1755
+ expect(result).toEqual([{ name: 'metric1' }]);
1756
+ });
1757
+
1758
+ // Test materializationInfo
1759
+ it('calls materializationInfo correctly', async () => {
1760
+ fetch.mockResponseOnce(JSON.stringify({ info: 'test' }));
1761
+ const result = await DataJunctionAPI.materializationInfo();
1762
+ expect(fetch).toHaveBeenCalledWith(
1763
+ 'http://localhost:8000/materialization/info',
1764
+ expect.objectContaining({ credentials: 'include' }),
1765
+ );
1766
+ expect(result).toEqual({ info: 'test' });
1767
+ });
1768
+
1769
+ // Test revalidate
1770
+ it('calls revalidate correctly', async () => {
1771
+ fetch.mockResponseOnce(JSON.stringify({ status: 'valid' }));
1772
+ const result = await DataJunctionAPI.revalidate('default.node1');
1773
+ expect(fetch).toHaveBeenCalledWith(
1774
+ 'http://localhost:8000/nodes/default.node1/validate',
1775
+ expect.objectContaining({
1776
+ method: 'POST',
1777
+ headers: {
1778
+ 'Content-Type': 'application/json',
1779
+ },
1780
+ }),
1781
+ );
1782
+ expect(result).toEqual({ status: 'valid' });
1783
+ });
1784
+
1785
+ // Test getMetric (lines 322-360) - this is a GraphQL query
1786
+ it('calls getMetric correctly', async () => {
1787
+ fetch.mockResponseOnce(
1788
+ JSON.stringify({
1789
+ data: {
1790
+ findNodes: [
1791
+ {
1792
+ name: 'system.test.metric',
1793
+ current: {
1794
+ metricMetadata: { direction: 'higher_is_better' },
1795
+ },
1796
+ },
1797
+ ],
1798
+ },
1799
+ }),
1800
+ );
1801
+
1802
+ const result = await DataJunctionAPI.getMetric('system.test.metric');
1803
+ expect(result).toHaveProperty('name');
1804
+ });
1805
+
1806
+ // Test system metrics APIs
1807
+ it('calls system.node_counts_by_active correctly', async () => {
1808
+ fetch.mockResponseOnce(
1809
+ JSON.stringify([
1810
+ [
1811
+ { col: 'system.dj.is_active.active_id', value: true },
1812
+ { col: 'system.dj.number_of_nodes', value: 100 },
1813
+ ],
1814
+ ]),
1815
+ );
1816
+
1817
+ const result = await DataJunctionAPI.system.node_counts_by_active();
1818
+ expect(result).toEqual([{ name: 'true', value: 100 }]);
1819
+ });
1820
+
1821
+ it('calls system.node_counts_by_type correctly', async () => {
1822
+ fetch.mockResponseOnce(
1823
+ JSON.stringify([
1824
+ [
1825
+ { col: 'system.dj.node_type.type', value: 'metric' },
1826
+ { col: 'system.dj.number_of_nodes', value: 50 },
1827
+ ],
1828
+ ]),
1829
+ );
1830
+
1831
+ const result = await DataJunctionAPI.system.node_counts_by_type();
1832
+ expect(result).toEqual([{ name: 'metric', value: 50 }]);
1833
+ });
1834
+
1835
+ it('calls system.node_counts_by_status correctly', async () => {
1836
+ fetch.mockResponseOnce(
1837
+ JSON.stringify([
1838
+ [
1839
+ { col: 'system.dj.nodes.status', value: 'valid' },
1840
+ { col: 'system.dj.number_of_nodes', value: 80 },
1841
+ ],
1842
+ ]),
1843
+ );
1844
+
1845
+ const result = await DataJunctionAPI.system.node_counts_by_status();
1846
+ expect(result).toEqual([{ name: 'valid', value: 80 }]);
1847
+ });
1848
+
1849
+ it('calls system.nodes_without_description correctly', async () => {
1850
+ fetch.mockResponseOnce(
1851
+ JSON.stringify([
1852
+ [
1853
+ { col: 'system.dj.node_type.type', value: 'transform' },
1854
+ { col: 'system.dj.node_without_description', value: 10 },
1855
+ ],
1856
+ ]),
1857
+ );
1858
+
1859
+ const result = await DataJunctionAPI.system.nodes_without_description();
1860
+ expect(result).toEqual([{ name: 'transform', value: 10 }]);
1861
+ });
1862
+
1863
+ it('calls system.materialization_counts_by_type correctly', async () => {
1864
+ fetch.mockResponseOnce(
1865
+ JSON.stringify([
1866
+ [
1867
+ { col: 'system.dj.node_type.type', value: 'cube' },
1868
+ { col: 'system.dj.number_of_materializations', value: 5 },
1869
+ ],
1870
+ ]),
1871
+ );
1872
+
1873
+ const result =
1874
+ await DataJunctionAPI.system.materialization_counts_by_type();
1875
+ expect(result).toEqual([{ name: 'cube', value: 5 }]);
1876
+ });
1877
+
1878
+ it('calls system.dimensions correctly', async () => {
1879
+ fetch.mockResponseOnce(JSON.stringify(['dim1', 'dim2']));
1880
+
1881
+ const result = await DataJunctionAPI.system.dimensions();
1882
+ expect(fetch).toHaveBeenCalledWith(
1883
+ 'http://localhost:8000/system/dimensions',
1884
+ expect.objectContaining({ credentials: 'include' }),
1885
+ );
1886
+ expect(result).toEqual(['dim1', 'dim2']);
1887
+ });
1888
+
1889
+ it('calls system.node_trends correctly', async () => {
1890
+ fetch.mockResponseOnce(
1891
+ JSON.stringify([
1892
+ [
1893
+ { col: 'system.dj.nodes.created_at_week', value: 20240101 },
1894
+ { col: 'system.dj.node_type.type', value: 'metric' },
1895
+ { col: 'system.dj.number_of_nodes', value: 10 },
1896
+ ],
1897
+ ]),
1898
+ );
1899
+
1900
+ const result = await DataJunctionAPI.system.node_trends();
1901
+ expect(result.length).toBeGreaterThan(0);
1902
+ expect(result[0]).toHaveProperty('date');
1903
+ expect(result[0]).toHaveProperty('metric');
1904
+ });
1905
+
1906
+ // Test querySystemMetricSingleDimension edge cases
1907
+ it('handles missing values in querySystemMetricSingleDimension', async () => {
1908
+ fetch.mockResponseOnce(
1909
+ JSON.stringify([
1910
+ [
1911
+ { col: 'some_dimension', value: null },
1912
+ { col: 'some_metric', value: undefined },
1913
+ ],
1914
+ ]),
1915
+ );
1916
+
1917
+ const result = await DataJunctionAPI.querySystemMetricSingleDimension({
1918
+ metric: 'some_metric',
1919
+ dimension: 'some_dimension',
1920
+ });
1921
+
1922
+ expect(result[0].name).toBe('unknown');
1923
+ expect(result[0].value).toBe(0);
1924
+ });
1925
+
1926
+ // Test getNodeForEditing (lines 266-319)
1927
+ it('calls getNodeForEditing correctly', async () => {
1928
+ fetch.mockResponseOnce(
1929
+ JSON.stringify({
1930
+ data: {
1931
+ findNodes: [
1932
+ {
1933
+ name: 'default.node1',
1934
+ type: 'transform',
1935
+ current: {
1936
+ displayName: 'Node 1',
1937
+ description: 'Test node',
1938
+ query: 'SELECT * FROM table',
1939
+ },
1940
+ },
1941
+ ],
1942
+ },
1943
+ }),
1944
+ );
1945
+
1946
+ const result = await DataJunctionAPI.getNodeForEditing('default.node1');
1947
+ expect(result).toHaveProperty('name', 'default.node1');
1948
+ });
1949
+
1950
+ it('returns null when getNodeForEditing finds no nodes', async () => {
1951
+ fetch.mockResponseOnce(
1952
+ JSON.stringify({
1953
+ data: {
1954
+ findNodes: [],
1955
+ },
1956
+ }),
1957
+ );
1958
+
1959
+ const result = await DataJunctionAPI.getNodeForEditing('nonexistent');
1960
+ expect(result).toBeNull();
1961
+ });
1962
+
1963
+ // Test getCubeForEditing (lines 363-410)
1964
+ it('calls getCubeForEditing correctly', async () => {
1965
+ fetch.mockResponseOnce(
1966
+ JSON.stringify({
1967
+ data: {
1968
+ findNodes: [
1969
+ {
1970
+ name: 'default.cube1',
1971
+ type: 'cube',
1972
+ current: {
1973
+ displayName: 'Cube 1',
1974
+ description: 'Test cube',
1975
+ cubeMetrics: [{ name: 'metric1' }],
1976
+ cubeDimensions: [{ name: 'dim1', attribute: 'attr1' }],
1977
+ },
1978
+ },
1979
+ ],
1980
+ },
1981
+ }),
1982
+ );
1983
+
1984
+ const result = await DataJunctionAPI.getCubeForEditing('default.cube1');
1985
+ expect(result).toHaveProperty('name', 'default.cube1');
1986
+ expect(result.current.cubeMetrics).toHaveLength(1);
1987
+ });
1988
+
1989
+ it('returns null when getCubeForEditing finds no nodes', async () => {
1990
+ fetch.mockResponseOnce(
1991
+ JSON.stringify({
1992
+ data: {
1993
+ findNodes: [],
1994
+ },
1995
+ }),
1996
+ );
1997
+
1998
+ const result = await DataJunctionAPI.getCubeForEditing('nonexistent');
1999
+ expect(result).toBeNull();
2000
+ });
2001
+
2002
+ // Test logout (lines 225-230)
2003
+ it('calls logout correctly', async () => {
2004
+ fetch.mockResponseOnce('', { status: 200 });
2005
+
2006
+ await DataJunctionAPI.logout();
2007
+ expect(fetch).toHaveBeenCalledWith(
2008
+ 'http://localhost:8000/logout/',
2009
+ expect.objectContaining({
2010
+ method: 'POST',
2011
+ credentials: 'include',
2012
+ }),
2013
+ );
2014
+ });
2015
+
2016
+ // Test catalogs (lines 232-238)
2017
+ it('calls catalogs correctly', async () => {
2018
+ fetch.mockResponseOnce(
2019
+ JSON.stringify([{ name: 'catalog1' }, { name: 'catalog2' }]),
2020
+ );
2021
+
2022
+ const result = await DataJunctionAPI.catalogs();
2023
+ expect(fetch).toHaveBeenCalledWith(
2024
+ 'http://localhost:8000/catalogs',
2025
+ expect.objectContaining({ credentials: 'include' }),
2026
+ );
2027
+ expect(result).toHaveLength(2);
2028
+ });
2029
+
2030
+ // Test sql with array filters (lines 755-756)
2031
+ it('handles array filters in sql correctly', async () => {
2032
+ fetch.mockResponseOnce(JSON.stringify({ sql: 'SELECT * FROM table' }));
2033
+
2034
+ await DataJunctionAPI.sql('default.metric1', {
2035
+ dimensions: ['dim1', 'dim2'],
2036
+ filters: ['filter1', 'filter2'],
2037
+ });
2038
+
2039
+ const callUrl = fetch.mock.calls[0][0];
2040
+ expect(callUrl).toContain('dimensions=dim1');
2041
+ expect(callUrl).toContain('filters=filter1');
2042
+ });
2043
+
2044
+ // Test nodeData with null selection (lines 831-835)
2045
+ it('handles null selection in nodeData', async () => {
2046
+ fetch.mockResponseOnce(JSON.stringify({ data: [] }));
2047
+
2048
+ await DataJunctionAPI.nodeData('default.node1', null);
2049
+
2050
+ const callUrl = fetch.mock.calls[0][0];
2051
+ expect(callUrl).toContain('limit=1000');
2052
+ expect(callUrl).toContain('async_=true');
2053
+ });
2054
+
2055
+ // Test streamNodeData with null selection (lines 889-893) - just test it returns EventSource
2056
+ it('handles null selection in streamNodeData', async () => {
2057
+ const eventSource = await DataJunctionAPI.streamNodeData(
2058
+ 'default.node1',
2059
+ null,
2060
+ );
2061
+
2062
+ expect(eventSource).toBeInstanceOf(EventSource);
2063
+
2064
+ // EventSource mock might not have close method in test environment
2065
+ if (typeof eventSource.close === 'function') {
2066
+ eventSource.close();
2067
+ }
2068
+ });
2069
+
2070
+ // Test setColumnDescription (lines 1027-1036)
2071
+ it('calls setColumnDescription correctly', async () => {
2072
+ fetch.mockResponseOnce(JSON.stringify({ message: 'success' }));
2073
+
2074
+ await DataJunctionAPI.setColumnDescription(
2075
+ 'default.node1',
2076
+ 'column1',
2077
+ 'New description',
2078
+ );
2079
+
2080
+ expect(fetch).toHaveBeenCalledWith(
2081
+ expect.stringContaining(
2082
+ '/nodes/default.node1/columns/column1/description',
2083
+ ),
2084
+ expect.objectContaining({
2085
+ method: 'PATCH',
2086
+ credentials: 'include',
2087
+ }),
2088
+ );
2089
+ });
2090
+
2091
+ // Test nodeDimensions (lines 1046-1050)
2092
+ it('calls nodeDimensions correctly', async () => {
2093
+ fetch.mockResponseOnce(JSON.stringify(['dim1', 'dim2']));
2094
+
2095
+ const result = await DataJunctionAPI.nodeDimensions('default.node1');
2096
+
2097
+ expect(fetch).toHaveBeenCalledWith(
2098
+ 'http://localhost:8000/nodes/default.node1/dimensions',
2099
+ expect.objectContaining({ credentials: 'include' }),
2100
+ );
2101
+ expect(result).toEqual(['dim1', 'dim2']);
2102
+ });
2103
+
2104
+ // Test users (lines 1194-1198)
2105
+ it('calls users correctly', async () => {
2106
+ fetch.mockResponseOnce(
2107
+ JSON.stringify([{ username: 'user1' }, { username: 'user2' }]),
2108
+ );
2109
+
2110
+ const result = await DataJunctionAPI.users();
2111
+
2112
+ expect(fetch).toHaveBeenCalledWith(
2113
+ 'http://localhost:8000/users?with_activity=true',
2114
+ expect.objectContaining({ credentials: 'include' }),
2115
+ );
2116
+ expect(result).toHaveLength(2);
2117
+ });
2118
+ });