@credibledata/cred-cli 0.0.6

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 (259) hide show
  1. package/.github/workflows/build.yml +23 -0
  2. package/.mocharc.json +10 -0
  3. package/.prettierignore +2 -0
  4. package/.prettierrc +4 -0
  5. package/README-DEV.md +40 -0
  6. package/README.md +11 -0
  7. package/bun.lock +1158 -0
  8. package/dist/client/api.js +7715 -0
  9. package/dist/client/api.js.map +1 -0
  10. package/dist/client/base.js +69 -0
  11. package/dist/client/base.js.map +1 -0
  12. package/dist/client/common.js +162 -0
  13. package/dist/client/common.js.map +1 -0
  14. package/dist/client/configuration.js +44 -0
  15. package/dist/client/configuration.js.map +1 -0
  16. package/dist/client/index.js +32 -0
  17. package/dist/client/index.js.map +1 -0
  18. package/dist/clientconfiguration.js +205 -0
  19. package/dist/clientconfiguration.js.map +1 -0
  20. package/dist/clientconfiguration.spec.js +381 -0
  21. package/dist/clientconfiguration.spec.js.map +1 -0
  22. package/dist/commands/add.js +66 -0
  23. package/dist/commands/add.js.map +1 -0
  24. package/dist/commands/connection/createconnections.js +89 -0
  25. package/dist/commands/connection/createconnections.js.map +1 -0
  26. package/dist/commands/connection/createconnections.spec.js +219 -0
  27. package/dist/commands/connection/createconnections.spec.js.map +1 -0
  28. package/dist/commands/connection/deleteconnection.js +52 -0
  29. package/dist/commands/connection/deleteconnection.js.map +1 -0
  30. package/dist/commands/connection/deleteconnection.spec.js +130 -0
  31. package/dist/commands/connection/deleteconnection.spec.js.map +1 -0
  32. package/dist/commands/connection/listconnections.js +50 -0
  33. package/dist/commands/connection/listconnections.js.map +1 -0
  34. package/dist/commands/connection/listconnections.spec.js +117 -0
  35. package/dist/commands/connection/listconnections.spec.js.map +1 -0
  36. package/dist/commands/get.js +28 -0
  37. package/dist/commands/get.js.map +1 -0
  38. package/dist/commands/group/addmember.js +75 -0
  39. package/dist/commands/group/addmember.js.map +1 -0
  40. package/dist/commands/group/addmember.spec.js +148 -0
  41. package/dist/commands/group/addmember.spec.js.map +1 -0
  42. package/dist/commands/group/creategroup.js +55 -0
  43. package/dist/commands/group/creategroup.js.map +1 -0
  44. package/dist/commands/group/creategroup.spec.js +122 -0
  45. package/dist/commands/group/creategroup.spec.js.map +1 -0
  46. package/dist/commands/group/deletegroup.js +48 -0
  47. package/dist/commands/group/deletegroup.js.map +1 -0
  48. package/dist/commands/group/deletegroup.spec.js +104 -0
  49. package/dist/commands/group/deletegroup.spec.js.map +1 -0
  50. package/dist/commands/group/getgroup.js +50 -0
  51. package/dist/commands/group/getgroup.js.map +1 -0
  52. package/dist/commands/group/getgroup.spec.js +111 -0
  53. package/dist/commands/group/getgroup.spec.js.map +1 -0
  54. package/dist/commands/group/listgroups.js +56 -0
  55. package/dist/commands/group/listgroups.js.map +1 -0
  56. package/dist/commands/group/listgroups.spec.js +118 -0
  57. package/dist/commands/group/listgroups.spec.js.map +1 -0
  58. package/dist/commands/group/removemember.js +69 -0
  59. package/dist/commands/group/removemember.js.map +1 -0
  60. package/dist/commands/group/removemember.spec.js +138 -0
  61. package/dist/commands/group/removemember.spec.js.map +1 -0
  62. package/dist/commands/groupaccesstoken/createToken.js +53 -0
  63. package/dist/commands/groupaccesstoken/createToken.js.map +1 -0
  64. package/dist/commands/groupaccesstoken/createToken.spec.js +127 -0
  65. package/dist/commands/groupaccesstoken/createToken.spec.js.map +1 -0
  66. package/dist/commands/groupaccesstoken/deleteToken.js +24 -0
  67. package/dist/commands/groupaccesstoken/deleteToken.js.map +1 -0
  68. package/dist/commands/groupaccesstoken/deleteToken.spec.js +103 -0
  69. package/dist/commands/groupaccesstoken/deleteToken.spec.js.map +1 -0
  70. package/dist/commands/groupaccesstoken/groupContainer.js +71 -0
  71. package/dist/commands/groupaccesstoken/groupContainer.js.map +1 -0
  72. package/dist/commands/groupaccesstoken/listTokens.js +25 -0
  73. package/dist/commands/groupaccesstoken/listTokens.js.map +1 -0
  74. package/dist/commands/groupaccesstoken/listTokens.spec.js +115 -0
  75. package/dist/commands/groupaccesstoken/listTokens.spec.js.map +1 -0
  76. package/dist/commands/list.js +75 -0
  77. package/dist/commands/list.js.map +1 -0
  78. package/dist/commands/misc/login.js +290 -0
  79. package/dist/commands/misc/login.js.map +1 -0
  80. package/dist/commands/misc/login.spec.js +243 -0
  81. package/dist/commands/misc/login.spec.js.map +1 -0
  82. package/dist/commands/misc/logout.js +11 -0
  83. package/dist/commands/misc/logout.js.map +1 -0
  84. package/dist/commands/misc/logout.spec.js +30 -0
  85. package/dist/commands/misc/logout.spec.js.map +1 -0
  86. package/dist/commands/misc/loopbackOAuth.js +110 -0
  87. package/dist/commands/misc/loopbackOAuth.js.map +1 -0
  88. package/dist/commands/misc/set-access-token.js +27 -0
  89. package/dist/commands/misc/set-access-token.js.map +1 -0
  90. package/dist/commands/misc/set-access-token.spec.js +65 -0
  91. package/dist/commands/misc/set-access-token.spec.js.map +1 -0
  92. package/dist/commands/package/deletepackage.js +54 -0
  93. package/dist/commands/package/deletepackage.js.map +1 -0
  94. package/dist/commands/package/deletepackage.spec.js +136 -0
  95. package/dist/commands/package/deletepackage.spec.js.map +1 -0
  96. package/dist/commands/package/listpackages.js +57 -0
  97. package/dist/commands/package/listpackages.js.map +1 -0
  98. package/dist/commands/package/listpackages.spec.js +129 -0
  99. package/dist/commands/package/listpackages.spec.js.map +1 -0
  100. package/dist/commands/package/publish.js +157 -0
  101. package/dist/commands/package/publish.js.map +1 -0
  102. package/dist/commands/package/publish.spec.js +511 -0
  103. package/dist/commands/package/publish.spec.js.map +1 -0
  104. package/dist/commands/package/updatepackage.js +75 -0
  105. package/dist/commands/package/updatepackage.js.map +1 -0
  106. package/dist/commands/package/updatepackage.spec.js +189 -0
  107. package/dist/commands/package/updatepackage.spec.js.map +1 -0
  108. package/dist/commands/project/createproject.js +75 -0
  109. package/dist/commands/project/createproject.js.map +1 -0
  110. package/dist/commands/project/createproject.spec.js +154 -0
  111. package/dist/commands/project/createproject.spec.js.map +1 -0
  112. package/dist/commands/project/deleteproject.js +49 -0
  113. package/dist/commands/project/deleteproject.js.map +1 -0
  114. package/dist/commands/project/deleteproject.spec.js +106 -0
  115. package/dist/commands/project/deleteproject.spec.js.map +1 -0
  116. package/dist/commands/project/getproject.js +44 -0
  117. package/dist/commands/project/getproject.js.map +1 -0
  118. package/dist/commands/project/getproject.spec.js +99 -0
  119. package/dist/commands/project/getproject.spec.js.map +1 -0
  120. package/dist/commands/project/listprojects.js +52 -0
  121. package/dist/commands/project/listprojects.js.map +1 -0
  122. package/dist/commands/project/listprojects.spec.js +111 -0
  123. package/dist/commands/project/listprojects.spec.js.map +1 -0
  124. package/dist/commands/project/setproject.js +26 -0
  125. package/dist/commands/project/setproject.js.map +1 -0
  126. package/dist/commands/project/setproject.spec.js +58 -0
  127. package/dist/commands/project/setproject.spec.js.map +1 -0
  128. package/dist/commands/project/updateproject.js +68 -0
  129. package/dist/commands/project/updateproject.js.map +1 -0
  130. package/dist/commands/project/updateproject.spec.js +129 -0
  131. package/dist/commands/project/updateproject.spec.js.map +1 -0
  132. package/dist/commands/rm.js +68 -0
  133. package/dist/commands/rm.js.map +1 -0
  134. package/dist/commands/set.js +17 -0
  135. package/dist/commands/set.js.map +1 -0
  136. package/dist/commands/update.js +42 -0
  137. package/dist/commands/update.js.map +1 -0
  138. package/dist/commands/version/archive-unarchive-version.js +79 -0
  139. package/dist/commands/version/archive-unarchive-version.js.map +1 -0
  140. package/dist/commands/version/archive-unarchive-version.spec.js +244 -0
  141. package/dist/commands/version/archive-unarchive-version.spec.js.map +1 -0
  142. package/dist/commands/version/listversions.js +46 -0
  143. package/dist/commands/version/listversions.js.map +1 -0
  144. package/dist/commands/version/listversions.spec.js +121 -0
  145. package/dist/commands/version/listversions.spec.js.map +1 -0
  146. package/dist/common/UserGroupId.js +112 -0
  147. package/dist/common/UserGroupId.js.map +1 -0
  148. package/dist/common/UserGroupId.spec.js +92 -0
  149. package/dist/common/UserGroupId.spec.js.map +1 -0
  150. package/dist/constants.js +90 -0
  151. package/dist/constants.js.map +1 -0
  152. package/dist/index.js +159 -0
  153. package/dist/index.js.map +1 -0
  154. package/dist/logger.js +77 -0
  155. package/dist/logger.js.map +1 -0
  156. package/dist/logger.spec.js +125 -0
  157. package/dist/logger.spec.js.map +1 -0
  158. package/dist/polyfills/file.polyfill.js +6 -0
  159. package/dist/polyfills/file.polyfill.js.map +1 -0
  160. package/dist/utils.js +110 -0
  161. package/dist/utils.js.map +1 -0
  162. package/dist/utils.spec.js +158 -0
  163. package/dist/utils.spec.js.map +1 -0
  164. package/dist/validator.js +53 -0
  165. package/dist/validator.js.map +1 -0
  166. package/dist/validator.spec.js +41 -0
  167. package/dist/validator.spec.js.map +1 -0
  168. package/docker/Dockerfile.ci +18 -0
  169. package/docker-compose.yml +18 -0
  170. package/eslint.config.mjs +45 -0
  171. package/generate-client.sh +27 -0
  172. package/mocha.setup.ts +4 -0
  173. package/openapitools.json +7 -0
  174. package/package.json +75 -0
  175. package/src/client/.openapi-generator/FILES +9 -0
  176. package/src/client/.openapi-generator/VERSION +1 -0
  177. package/src/client/.openapi-generator-ignore +23 -0
  178. package/src/client/api.ts +9729 -0
  179. package/src/client/base.ts +86 -0
  180. package/src/client/common.ts +150 -0
  181. package/src/client/configuration.ts +110 -0
  182. package/src/client/git_push.sh +57 -0
  183. package/src/client/index.ts +18 -0
  184. package/src/clientconfiguration.spec.ts +490 -0
  185. package/src/clientconfiguration.ts +263 -0
  186. package/src/commands/add.ts +125 -0
  187. package/src/commands/connection/createconnections.spec.ts +403 -0
  188. package/src/commands/connection/createconnections.ts +109 -0
  189. package/src/commands/connection/deleteconnection.spec.ts +189 -0
  190. package/src/commands/connection/deleteconnection.ts +57 -0
  191. package/src/commands/connection/listconnections.spec.ts +132 -0
  192. package/src/commands/connection/listconnections.ts +58 -0
  193. package/src/commands/get.ts +31 -0
  194. package/src/commands/group/addmember.spec.ts +257 -0
  195. package/src/commands/group/addmember.ts +85 -0
  196. package/src/commands/group/creategroup.spec.ts +146 -0
  197. package/src/commands/group/creategroup.ts +54 -0
  198. package/src/commands/group/deletegroup.spec.ts +120 -0
  199. package/src/commands/group/deletegroup.ts +45 -0
  200. package/src/commands/group/getgroup.spec.ts +129 -0
  201. package/src/commands/group/getgroup.ts +47 -0
  202. package/src/commands/group/listgroups.spec.ts +137 -0
  203. package/src/commands/group/listgroups.ts +45 -0
  204. package/src/commands/group/removemember.spec.ts +224 -0
  205. package/src/commands/group/removemember.ts +75 -0
  206. package/src/commands/groupaccesstoken/createToken.spec.ts +211 -0
  207. package/src/commands/groupaccesstoken/createToken.ts +72 -0
  208. package/src/commands/groupaccesstoken/deleteToken.spec.ts +147 -0
  209. package/src/commands/groupaccesstoken/deleteToken.ts +29 -0
  210. package/src/commands/groupaccesstoken/groupContainer.ts +83 -0
  211. package/src/commands/groupaccesstoken/listTokens.spec.ts +147 -0
  212. package/src/commands/groupaccesstoken/listTokens.ts +29 -0
  213. package/src/commands/list.ts +131 -0
  214. package/src/commands/misc/login.spec.ts +213 -0
  215. package/src/commands/misc/login.ts +325 -0
  216. package/src/commands/misc/logout.spec.ts +32 -0
  217. package/src/commands/misc/logout.ts +9 -0
  218. package/src/commands/misc/loopbackOAuth.ts +127 -0
  219. package/src/commands/misc/set-access-token.spec.ts +95 -0
  220. package/src/commands/misc/set-access-token.ts +32 -0
  221. package/src/commands/package/deletepackage.spec.ts +164 -0
  222. package/src/commands/package/deletepackage.ts +58 -0
  223. package/src/commands/package/listpackages.spec.ts +147 -0
  224. package/src/commands/package/listpackages.ts +67 -0
  225. package/src/commands/package/publish.spec.ts +597 -0
  226. package/src/commands/package/publish.ts +211 -0
  227. package/src/commands/package/updatepackage.spec.ts +319 -0
  228. package/src/commands/package/updatepackage.ts +95 -0
  229. package/src/commands/project/createproject.spec.ts +247 -0
  230. package/src/commands/project/createproject.ts +70 -0
  231. package/src/commands/project/deleteproject.spec.ts +123 -0
  232. package/src/commands/project/deleteproject.ts +48 -0
  233. package/src/commands/project/getproject.spec.ts +109 -0
  234. package/src/commands/project/getproject.ts +43 -0
  235. package/src/commands/project/listprojects.spec.ts +121 -0
  236. package/src/commands/project/listprojects.ts +62 -0
  237. package/src/commands/project/setproject.spec.ts +63 -0
  238. package/src/commands/project/setproject.ts +14 -0
  239. package/src/commands/project/updateproject.spec.ts +217 -0
  240. package/src/commands/project/updateproject.ts +82 -0
  241. package/src/commands/rm.ts +101 -0
  242. package/src/commands/set.ts +16 -0
  243. package/src/commands/update.ts +64 -0
  244. package/src/commands/version/archive-unarchive-version.spec.ts +456 -0
  245. package/src/commands/version/archive-unarchive-version.ts +111 -0
  246. package/src/commands/version/listversions.spec.ts +138 -0
  247. package/src/commands/version/listversions.ts +45 -0
  248. package/src/common/UserGroupId.spec.ts +117 -0
  249. package/src/common/UserGroupId.ts +137 -0
  250. package/src/constants.ts +116 -0
  251. package/src/index.ts +209 -0
  252. package/src/logger.spec.ts +158 -0
  253. package/src/logger.ts +86 -0
  254. package/src/polyfills/file.polyfill.ts +6 -0
  255. package/src/utils.spec.ts +135 -0
  256. package/src/utils.ts +115 -0
  257. package/src/validator.spec.ts +51 -0
  258. package/src/validator.ts +44 -0
  259. package/tsconfig.json +17 -0
@@ -0,0 +1,490 @@
1
+ import { expect } from "chai";
2
+ import fs from "fs";
3
+ import os from "os";
4
+ import { join } from "path";
5
+ import sinon from "sinon";
6
+ import { ClientConfiguration } from "./clientconfiguration";
7
+ import { ALL_ENVIRONMENTS, type Environment } from "./constants";
8
+ import { logger } from "./logger";
9
+
10
+ /** Expected `configPerEnvironment` shape after `loadConfig()` (every env has an object). */
11
+ function fullPerEnv(
12
+ filled: Partial<Record<Environment, Record<string, unknown>>> = {},
13
+ ): Record<string, Record<string, unknown>> {
14
+ const out: Record<string, Record<string, unknown>> = {};
15
+ for (const env of ALL_ENVIRONMENTS) {
16
+ out[env] = filled[env] != null ? { ...filled[env]! } : {};
17
+ }
18
+ return out;
19
+ }
20
+
21
+ describe("ClientConfiguration", () => {
22
+ let clientConfig: ClientConfiguration;
23
+ let readFileSyncStub: sinon.SinonStub;
24
+ let writeFileSyncStub: sinon.SinonStub;
25
+ let customConfigPath: string;
26
+
27
+ beforeEach(() => {
28
+ customConfigPath = join(os.homedir(), ".cred_test");
29
+ clientConfig = new ClientConfiguration(customConfigPath);
30
+
31
+ fs.writeFileSync(customConfigPath, "{}");
32
+
33
+ readFileSyncStub = sinon.stub(fs, "readFileSync");
34
+ writeFileSyncStub = sinon.stub(fs, "writeFileSync");
35
+ });
36
+
37
+ afterEach(() => {
38
+ sinon.restore();
39
+
40
+ if (fs.existsSync(customConfigPath)) {
41
+ fs.unlinkSync(customConfigPath);
42
+ }
43
+ });
44
+
45
+ it("should load the default config if the file does not exist", () => {
46
+ readFileSyncStub.throws(new Error("File not found"));
47
+ clientConfig.loadConfig();
48
+ expect(clientConfig["config"]).to.deep.equal({
49
+ configPerEnvironment: fullPerEnv(),
50
+ activeEnvironment: "production",
51
+ });
52
+ });
53
+
54
+ it("should load the config file correctly", () => {
55
+ const mockConfig = {
56
+ configPerEnvironment: {
57
+ production: { project: "test-project" },
58
+ staging: {},
59
+ localhost: {},
60
+ },
61
+ activeEnvironment: "production",
62
+ };
63
+ readFileSyncStub.returns(JSON.stringify(mockConfig));
64
+ clientConfig.loadConfig();
65
+ expect(clientConfig["config"]).to.deep.equal({
66
+ configPerEnvironment: fullPerEnv({
67
+ production: { project: "test-project" },
68
+ }),
69
+ activeEnvironment: "production",
70
+ });
71
+ });
72
+
73
+ it("should allow setAccessToken when activeEnvironment had no bucket on disk (e.g. videoamp)", () => {
74
+ const mockConfig = {
75
+ configPerEnvironment: {
76
+ production: {},
77
+ staging: {},
78
+ dev: {},
79
+ localhost: {},
80
+ "e2e-test": {},
81
+ },
82
+ activeEnvironment: "videoamp",
83
+ };
84
+ readFileSyncStub.returns(JSON.stringify(mockConfig));
85
+ clientConfig.setAccessToken(true, "user-jwt");
86
+ const written = JSON.parse(writeFileSyncStub.lastCall.args[1] as string);
87
+ expect(written.configPerEnvironment.videoamp.accessToken).to.equal(
88
+ "user-jwt",
89
+ );
90
+ expect(written.configPerEnvironment.videoamp.isServiceAccountToken).to.be
91
+ .false;
92
+ });
93
+
94
+ it("should set and get the project correctly", () => {
95
+ readFileSyncStub.throws(new Error("File not found"));
96
+ clientConfig.setProject("new-project");
97
+
98
+ expect(
99
+ writeFileSyncStub.calledWith(
100
+ customConfigPath,
101
+ JSON.stringify({
102
+ configPerEnvironment: fullPerEnv({
103
+ production: { project: "new-project" },
104
+ }),
105
+ activeEnvironment: "production",
106
+ }),
107
+ ),
108
+ ).to.be.true;
109
+ });
110
+
111
+ it("should set and get the access token correctly", () => {
112
+ readFileSyncStub.throws(new Error("File not found"));
113
+ clientConfig.setAccessToken(false, "token-123");
114
+
115
+ expect(
116
+ writeFileSyncStub.calledWith(
117
+ customConfigPath,
118
+ JSON.stringify({
119
+ configPerEnvironment: fullPerEnv({
120
+ production: {
121
+ isServiceAccountToken: true,
122
+ accessToken: "token-123",
123
+ },
124
+ }),
125
+ activeEnvironment: "production",
126
+ }),
127
+ ),
128
+ ).to.be.true;
129
+ });
130
+
131
+ it("should clear auth when setting access token", () => {
132
+ readFileSyncStub.returns(
133
+ JSON.stringify({ auth: { username: "user", password: "pass" } }),
134
+ );
135
+ clientConfig.setAccessToken(true, "new-token");
136
+ expect(
137
+ writeFileSyncStub.calledWith(
138
+ customConfigPath,
139
+ JSON.stringify({
140
+ configPerEnvironment: fullPerEnv({
141
+ production: {
142
+ isServiceAccountToken: false,
143
+ accessToken: "new-token",
144
+ },
145
+ }),
146
+ activeEnvironment: "production",
147
+ }),
148
+ ),
149
+ ).to.be.true;
150
+ });
151
+
152
+ it("should set and get organization", () => {
153
+ readFileSyncStub.throws(new Error("File not found"));
154
+ clientConfig.setOrganization("my-org");
155
+
156
+ expect(
157
+ writeFileSyncStub.calledWith(
158
+ customConfigPath,
159
+ JSON.stringify({
160
+ configPerEnvironment: fullPerEnv({
161
+ production: { organization: "my-org" },
162
+ }),
163
+ activeEnvironment: "production",
164
+ }),
165
+ ),
166
+ ).to.be.true;
167
+ });
168
+
169
+ it("should set and get refresh token", () => {
170
+ readFileSyncStub.throws(new Error("File not found"));
171
+ clientConfig.setRefreshToken("refresh-token-123");
172
+
173
+ expect(
174
+ writeFileSyncStub.calledWith(
175
+ customConfigPath,
176
+ JSON.stringify({
177
+ configPerEnvironment: fullPerEnv({
178
+ production: { refreshToken: "refresh-token-123" },
179
+ }),
180
+ activeEnvironment: "production",
181
+ }),
182
+ ),
183
+ ).to.be.true;
184
+ });
185
+
186
+ it("should set and get VSCode user", () => {
187
+ readFileSyncStub.throws(new Error("File not found"));
188
+ clientConfig.setVSCodeUser("testuser", "test@example.com");
189
+
190
+ expect(
191
+ writeFileSyncStub.calledWith(
192
+ customConfigPath,
193
+ JSON.stringify({
194
+ configPerEnvironment: fullPerEnv({
195
+ production: {
196
+ vsCodeUser: {
197
+ username: "testuser",
198
+ email: "test@example.com",
199
+ },
200
+ },
201
+ }),
202
+ activeEnvironment: "production",
203
+ }),
204
+ ),
205
+ ).to.be.true;
206
+ });
207
+
208
+ it("should set and get JWT access token", () => {
209
+ readFileSyncStub.throws(new Error("File not found"));
210
+ clientConfig.setJwtAccessToken("jwt-token-abc");
211
+
212
+ expect(
213
+ writeFileSyncStub.calledWith(
214
+ customConfigPath,
215
+ JSON.stringify({
216
+ configPerEnvironment: fullPerEnv({
217
+ production: { jwtAccessToken: "jwt-token-abc" },
218
+ }),
219
+ activeEnvironment: "production",
220
+ }),
221
+ ),
222
+ ).to.be.true;
223
+ });
224
+
225
+ it("should return true for doesConfigExists when environment config exists", () => {
226
+ const mockConfig = {
227
+ configPerEnvironment: {
228
+ production: { project: "test" },
229
+ staging: {},
230
+ localhost: {},
231
+ },
232
+ activeEnvironment: "production",
233
+ };
234
+ readFileSyncStub.returns(JSON.stringify(mockConfig));
235
+
236
+ expect(clientConfig.doesConfigExists()).to.be.true;
237
+ });
238
+
239
+ it("should destroy config by resetting environment config to empty", () => {
240
+ const mockConfig = {
241
+ configPerEnvironment: {
242
+ production: { organization: "my-org", project: "my-proj" },
243
+ staging: {},
244
+ localhost: {},
245
+ },
246
+ activeEnvironment: "production",
247
+ };
248
+ readFileSyncStub.returns(JSON.stringify(mockConfig));
249
+ clientConfig.destroyConfig();
250
+
251
+ const lastWrite = writeFileSyncStub.lastCall.args[1] as string;
252
+ expect(JSON.parse(lastWrite)).to.deep.equal({
253
+ configPerEnvironment: fullPerEnv(),
254
+ activeEnvironment: "production",
255
+ });
256
+ });
257
+
258
+ it("should set and get environment", () => {
259
+ readFileSyncStub.throws(new Error("File not found"));
260
+ clientConfig.setEnvironment("staging");
261
+
262
+ expect(
263
+ writeFileSyncStub.calledWith(
264
+ customConfigPath,
265
+ JSON.stringify({
266
+ configPerEnvironment: fullPerEnv(),
267
+ activeEnvironment: "staging",
268
+ }),
269
+ ),
270
+ ).to.be.true;
271
+ });
272
+
273
+ it("should return headers with Bearer token for user tokens", () => {
274
+ const mockConfig = {
275
+ configPerEnvironment: {
276
+ production: {
277
+ accessToken: "user-token",
278
+ isServiceAccountToken: false,
279
+ },
280
+ staging: {},
281
+ localhost: {},
282
+ },
283
+ activeEnvironment: "production",
284
+ };
285
+ readFileSyncStub.returns(JSON.stringify(mockConfig));
286
+ clientConfig.loadConfig();
287
+
288
+ const headers = clientConfig.getHeaders();
289
+ expect(headers.Authorization).to.equal("Bearer user-token");
290
+ });
291
+
292
+ it("should return headers with ApiKey for service account tokens", () => {
293
+ const mockConfig = {
294
+ configPerEnvironment: {
295
+ production: {
296
+ accessToken: "service-token",
297
+ isServiceAccountToken: true,
298
+ },
299
+ staging: {},
300
+ localhost: {},
301
+ },
302
+ activeEnvironment: "production",
303
+ };
304
+ readFileSyncStub.returns(JSON.stringify(mockConfig));
305
+ clientConfig.loadConfig();
306
+
307
+ const headers = clientConfig.getHeaders();
308
+ expect(headers.Authorization).to.equal("ApiKey service-token");
309
+ });
310
+
311
+ it("should reset to defaults when config file is corrupted (missing activeEnvironment)", () => {
312
+ readFileSyncStub.returns(JSON.stringify({ configPerEnvironment: {} }));
313
+ clientConfig.loadConfig();
314
+
315
+ expect(clientConfig["config"].activeEnvironment).to.equal("production");
316
+ });
317
+
318
+ describe("isTokenValid", () => {
319
+ it("should return invalid when no token exists", () => {
320
+ const mockConfig = {
321
+ configPerEnvironment: {
322
+ production: {},
323
+ staging: {},
324
+ localhost: {},
325
+ },
326
+ activeEnvironment: "production",
327
+ };
328
+ readFileSyncStub.returns(JSON.stringify(mockConfig));
329
+
330
+ const result = clientConfig.isTokenValid();
331
+ expect(result.valid).to.be.false;
332
+ expect(result.reason).to.equal("No token found. Please login.");
333
+ });
334
+
335
+ it("should return valid for service account tokens", () => {
336
+ const mockConfig = {
337
+ configPerEnvironment: {
338
+ production: {
339
+ accessToken: "some-api-key",
340
+ isServiceAccountToken: true,
341
+ },
342
+ staging: {},
343
+ localhost: {},
344
+ },
345
+ activeEnvironment: "production",
346
+ };
347
+ readFileSyncStub.returns(JSON.stringify(mockConfig));
348
+
349
+ const result = clientConfig.isTokenValid();
350
+ expect(result.valid).to.be.true;
351
+ });
352
+
353
+ it("should return invalid for expired JWT", () => {
354
+ // Create a JWT with an expired exp claim
355
+ const payload = { exp: Math.floor(Date.now() / 1000) - 3600 };
356
+ const fakeJwt =
357
+ Buffer.from(JSON.stringify({ alg: "HS256" })).toString(
358
+ "base64url",
359
+ ) +
360
+ "." +
361
+ Buffer.from(JSON.stringify(payload)).toString("base64url") +
362
+ ".signature";
363
+
364
+ const mockConfig = {
365
+ configPerEnvironment: {
366
+ production: {
367
+ accessToken: fakeJwt,
368
+ isServiceAccountToken: false,
369
+ },
370
+ staging: {},
371
+ localhost: {},
372
+ },
373
+ activeEnvironment: "production",
374
+ };
375
+ readFileSyncStub.returns(JSON.stringify(mockConfig));
376
+
377
+ const result = clientConfig.isTokenValid();
378
+ expect(result.valid).to.be.false;
379
+ expect(result.reason).to.equal("Token expired. Please login again.");
380
+ });
381
+
382
+ it("should return valid for non-expired JWT", () => {
383
+ const payload = { exp: Math.floor(Date.now() / 1000) + 3600 };
384
+ const fakeJwt =
385
+ Buffer.from(JSON.stringify({ alg: "HS256" })).toString(
386
+ "base64url",
387
+ ) +
388
+ "." +
389
+ Buffer.from(JSON.stringify(payload)).toString("base64url") +
390
+ ".signature";
391
+
392
+ const mockConfig = {
393
+ configPerEnvironment: {
394
+ production: {
395
+ accessToken: fakeJwt,
396
+ isServiceAccountToken: false,
397
+ },
398
+ staging: {},
399
+ localhost: {},
400
+ },
401
+ activeEnvironment: "production",
402
+ };
403
+ readFileSyncStub.returns(JSON.stringify(mockConfig));
404
+
405
+ const result = clientConfig.isTokenValid();
406
+ expect(result.valid).to.be.true;
407
+ });
408
+
409
+ it("should return invalid for malformed token", () => {
410
+ const mockConfig = {
411
+ configPerEnvironment: {
412
+ production: {
413
+ accessToken: "not-a-valid-jwt",
414
+ isServiceAccountToken: false,
415
+ },
416
+ staging: {},
417
+ localhost: {},
418
+ },
419
+ activeEnvironment: "production",
420
+ };
421
+ readFileSyncStub.returns(JSON.stringify(mockConfig));
422
+
423
+ const result = clientConfig.isTokenValid();
424
+ expect(result.valid).to.be.false;
425
+ expect(result.reason).to.equal(
426
+ "Invalid token format. Please login again.",
427
+ );
428
+ });
429
+ });
430
+
431
+ describe("printStatus", () => {
432
+ let loggerStub: sinon.SinonStubbedInstance<typeof logger>;
433
+ let consoleLogStub: sinon.SinonStub;
434
+
435
+ beforeEach(() => {
436
+ loggerStub = sinon.stub(logger);
437
+ consoleLogStub = sinon.stub(console, "log");
438
+ });
439
+
440
+ it("should print org and project when token is valid", () => {
441
+ const payload = { exp: Math.floor(Date.now() / 1000) + 3600 };
442
+ const fakeJwt =
443
+ Buffer.from(JSON.stringify({ alg: "HS256" })).toString(
444
+ "base64url",
445
+ ) +
446
+ "." +
447
+ Buffer.from(JSON.stringify(payload)).toString("base64url") +
448
+ ".signature";
449
+
450
+ const mockConfig = {
451
+ configPerEnvironment: {
452
+ production: {
453
+ accessToken: fakeJwt,
454
+ isServiceAccountToken: false,
455
+ organization: "my-org",
456
+ project: "my-project",
457
+ },
458
+ staging: {},
459
+ localhost: {},
460
+ },
461
+ activeEnvironment: "production",
462
+ };
463
+ readFileSyncStub.returns(JSON.stringify(mockConfig));
464
+
465
+ clientConfig.printStatus();
466
+
467
+ expect(loggerStub.info.calledWith(sinon.match("Organization:"))).to.be
468
+ .true;
469
+ expect(loggerStub.info.calledWith(sinon.match("Project:"))).to.be.true;
470
+ });
471
+
472
+ it("should print error when token is invalid", () => {
473
+ const mockConfig = {
474
+ configPerEnvironment: {
475
+ production: {},
476
+ staging: {},
477
+ localhost: {},
478
+ },
479
+ activeEnvironment: "production",
480
+ };
481
+ readFileSyncStub.returns(JSON.stringify(mockConfig));
482
+
483
+ clientConfig.printStatus();
484
+
485
+ expect(
486
+ loggerStub.error.calledWith(sinon.match("Failed to get status")),
487
+ ).to.be.true;
488
+ });
489
+ });
490
+ });