@raketa-cloud/admin 0.0.87 → 0.0.90

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 (31) hide show
  1. package/jest.config.js +22 -0
  2. package/package.json +2 -2
  3. package/src/cloud/ArticleRevisions.js +33 -5
  4. package/src/cloud/Articles.js +34 -4
  5. package/src/cloud/EventRevisions.js +33 -5
  6. package/src/cloud/Events.js +35 -5
  7. package/src/cloud/PageRevisions.js +31 -3
  8. package/src/cloud/Pages.js +34 -4
  9. package/src/cloud/documents/articlesDocuments.js +44 -3
  10. package/src/cloud/documents/eventsDocuments.js +43 -2
  11. package/src/cloud/documents/pagesDocuments.js +43 -2
  12. package/src/handlers/articles/TranslationsHandler.js +81 -15
  13. package/src/handlers/events/TranslationsHandler.js +80 -14
  14. package/src/handlers/pages/TranslationsHandler.js +84 -18
  15. package/src/helpers/AuthHelper.js +2 -2
  16. package/src/setupAuthentication.js +6 -1
  17. package/src/tests/handlers/TagsHandler.test/create.test.js +96 -0
  18. package/src/tests/handlers/TagsHandler.test/create_form.test.js +25 -0
  19. package/src/tests/handlers/TagsHandler.test/destroy.test.js +51 -0
  20. package/src/tests/handlers/TagsHandler.test/edit_form.test.js +60 -0
  21. package/src/tests/handlers/TagsHandler.test/index.test.js +37 -0
  22. package/src/tests/handlers/TagsHandler.test/update.test.js +105 -0
  23. package/src/tests/handlers/articles/TranslationHandler.test/create.test.js +85 -79
  24. package/src/tests/handlers/events/TranslationHandler.test/create.test.js +84 -78
  25. package/src/tests/handlers/pages/TranslationHandler.test/create.test.js +83 -72
  26. package/src/tests/helpers/AuthHelper.test.js +169 -0
  27. package/src/tests/helpers/FormBuildert.test.js +357 -0
  28. package/src/tests/helpers/Inflector.test.js +75 -0
  29. package/src/views/articles/translations/create_form.ejs +2 -0
  30. package/src/views/events/translations/create_form.ejs +2 -0
  31. package/src/views/pages/translations/create_form.ejs +3 -1
@@ -38,12 +38,14 @@ describe("Pages Translation Handler", () => {
38
38
  .mockImplementation(() => page);
39
39
 
40
40
  const translationErrors = [{ key: "title", message: "cannot be blank" }];
41
- const mockTranslate = jest
42
- .spyOn(ctx.client.pages, "translate")
43
- .mockImplementation(() => ({
44
- page: null,
45
- errors: translationErrors,
46
- }));
41
+ const mockTranslateBatch = jest
42
+ .spyOn(ctx.client.pages, "translateBatch")
43
+ .mockImplementation((parentId, locales) =>
44
+ locales.map((locale) => ({
45
+ page: null,
46
+ errors: translationErrors,
47
+ }))
48
+ );
47
49
 
48
50
  const mockFormatErrors = jest
49
51
  .spyOn(ctx.client.pages, "formatErrors")
@@ -52,7 +54,7 @@ describe("Pages Translation Handler", () => {
52
54
  const result = await TranslationsHandler.create(ctx);
53
55
 
54
56
  expect(mockFind).toHaveBeenCalledWith(page.id, "en");
55
- expect(mockTranslate).toHaveBeenCalled();
57
+ expect(mockTranslateBatch).toHaveBeenCalled();
56
58
  expect(mockFormatErrors).toHaveBeenCalledWith(translationErrors);
57
59
 
58
60
  expect(result).toEqual({
@@ -91,35 +93,30 @@ describe("Pages Translation Handler", () => {
91
93
  .spyOn(ctx.client.pages, "find")
92
94
  .mockImplementation(() => page);
93
95
 
94
- const mockTranslate = jest
95
- .spyOn(ctx.client.pages, "translate")
96
- .mockImplementation((parentId, locale) => ({
97
- page: { id: `translated-page-${locale}` },
98
- errors: null,
99
- }));
100
-
101
- const mockCreateRevision = jest
102
- .spyOn(ctx.client.pageRevisions, "create")
103
- .mockImplementation(() => ({ errors: null }));
96
+ const mockTranslateBatch = jest
97
+ .spyOn(ctx.client.pages, "translateBatch")
98
+ .mockImplementation((parentId, locales) =>
99
+ locales.map((locale) => ({
100
+ page: { id: `translated-page-${locale}` },
101
+ errors: null,
102
+ }))
103
+ );
104
+
105
+ const mockCreateRevisionBatch = jest
106
+ .spyOn(ctx.client.pageRevisions, "createBatch")
107
+ .mockImplementation((revisionInputs) =>
108
+ revisionInputs.map(({ locale }) => ({
109
+ revision: { id: `revision-${locale}` },
110
+ errors: null,
111
+ }))
112
+ );
104
113
 
105
114
  const result = await TranslationsHandler.create(ctx);
106
115
 
107
116
  expect(mockFind).toHaveBeenCalledWith(page.id, "en");
108
-
109
- expect(mockTranslate).toHaveBeenCalledTimes(2);
110
- expect(mockTranslate).toHaveBeenCalledWith(
117
+ expect(mockTranslateBatch).toHaveBeenCalledWith(
111
118
  page.id,
112
- "fr",
113
- expect.objectContaining({
114
- title: page.title,
115
- tags: [],
116
- visibility: "private",
117
- slug: page.slug,
118
- })
119
- );
120
- expect(mockTranslate).toHaveBeenCalledWith(
121
- page.id,
122
- "es",
119
+ ["fr", "es"],
123
120
  expect.objectContaining({
124
121
  title: page.title,
125
122
  tags: [],
@@ -128,16 +125,17 @@ describe("Pages Translation Handler", () => {
128
125
  })
129
126
  );
130
127
 
131
- expect(mockCreateRevision).toHaveBeenCalledTimes(2);
132
- expect(mockCreateRevision).toHaveBeenCalledWith("translated-page-fr", "fr");
133
- expect(mockCreateRevision).toHaveBeenCalledWith("translated-page-es", "es");
128
+ expect(mockCreateRevisionBatch).toHaveBeenCalledWith([
129
+ { pageId: "translated-page-fr", locale: "fr" },
130
+ { pageId: "translated-page-es", locale: "es" },
131
+ ]);
134
132
 
135
133
  expect(result).toEqual({
136
134
  redirect: `/en/pages/${page.id}/translations?translations=fr:translated-page-fr;es:translated-page-es`,
137
135
  });
138
136
  });
139
137
 
140
- test("create translations fails if any translation fails", async () => {
138
+ test("create translations if any translation fails", async () => {
141
139
  const page = Factory.build("page", {
142
140
  translations: {},
143
141
  categories: [{ id: "some-category-uno" }],
@@ -153,17 +151,22 @@ describe("Pages Translation Handler", () => {
153
151
  .spyOn(ctx.client.pages, "find")
154
152
  .mockImplementation(() => page);
155
153
 
156
- const translationResponses = {
157
- fr: { page: { id: "translated-page-fr" }, errors: null },
158
- es: {
159
- page: null,
160
- errors: [{ key: "title", message: "invalid for spanish" }],
161
- },
162
- };
163
-
164
- const mockTranslate = jest
165
- .spyOn(ctx.client.pages, "translate")
166
- .mockImplementation((parentId, locale) => translationResponses[locale]);
154
+ const mockTranslateBatch = jest
155
+ .spyOn(ctx.client.pages, "translateBatch")
156
+ .mockImplementation((parentId, locales) => [
157
+ {
158
+ page: {
159
+ id: "translated-page-fr",
160
+ slug: "page-revision",
161
+ visibility: "public",
162
+ },
163
+ errors: null,
164
+ },
165
+ {
166
+ page: null,
167
+ errors: [{ key: "title", message: "invalid for spanish" }],
168
+ },
169
+ ]);
167
170
 
168
171
  const mockFormatErrors = jest
169
172
  .spyOn(ctx.client.pages, "formatErrors")
@@ -178,21 +181,9 @@ describe("Pages Translation Handler", () => {
178
181
  const result = await TranslationsHandler.create(ctx);
179
182
 
180
183
  expect(mockFind).toHaveBeenCalledWith(page.id, "en");
181
-
182
- expect(mockTranslate).toHaveBeenCalledWith(
184
+ expect(mockTranslateBatch).toHaveBeenCalledWith(
183
185
  page.id,
184
- "fr",
185
- expect.objectContaining({
186
- title: page.title,
187
- tags: [],
188
- visibility: "private",
189
- slug: page.slug,
190
- })
191
- );
192
-
193
- expect(mockTranslate).toHaveBeenCalledWith(
194
- page.id,
195
- "es",
186
+ ["fr", "es"],
196
187
  expect.objectContaining({
197
188
  title: page.title,
198
189
  tags: [],
@@ -211,7 +202,13 @@ describe("Pages Translation Handler", () => {
211
202
  settings: page.settings,
212
203
  widgets: page.widgets,
213
204
  categories: ["some-category-uno"],
214
- translations: {},
205
+ translations: {
206
+ fr: {
207
+ id: "translated-page-fr",
208
+ slug: page.slug,
209
+ visibility: page.visibility,
210
+ },
211
+ },
215
212
  languages: ["fr", "es"],
216
213
  },
217
214
  blueprint: page,
@@ -236,17 +233,23 @@ describe("Pages Translation Handler", () => {
236
233
  .spyOn(ctx.client.pages, "find")
237
234
  .mockImplementation(() => page);
238
235
 
239
- const mockTranslate = jest
240
- .spyOn(ctx.client.pages, "translate")
241
- .mockImplementation(() => ({
242
- page: { id: "translated-page-id" },
243
- errors: null,
244
- }));
236
+ const mockTranslateBatch = jest
237
+ .spyOn(ctx.client.pages, "translateBatch")
238
+ .mockImplementation(() => [
239
+ {
240
+ page: {
241
+ id: "translated-page-fr",
242
+ slug: "page-revision",
243
+ visibility: "public",
244
+ },
245
+ errors: null,
246
+ },
247
+ ]);
245
248
 
246
249
  const revisionErrors = [{ key: "base", message: "something went wrong" }];
247
- const mockCreateRevision = jest
248
- .spyOn(ctx.client.pageRevisions, "create")
249
- .mockImplementation(() => ({ errors: revisionErrors }));
250
+ const mockCreateRevisionBatch = jest
251
+ .spyOn(ctx.client.pageRevisions, "createBatch")
252
+ .mockImplementation(() => [{ errors: revisionErrors }]);
250
253
 
251
254
  const mockFormatErrors = jest
252
255
  .spyOn(ctx.client.pages, "formatErrors")
@@ -254,7 +257,9 @@ describe("Pages Translation Handler", () => {
254
257
 
255
258
  const result = await TranslationsHandler.create(ctx);
256
259
 
257
- expect(mockCreateRevision).toHaveBeenCalledWith("translated-page-id", "fr");
260
+ expect(mockCreateRevisionBatch).toHaveBeenCalledWith([
261
+ { pageId: "translated-page-fr", locale: "fr" },
262
+ ]);
258
263
  expect(mockFormatErrors).toHaveBeenCalledWith(revisionErrors);
259
264
 
260
265
  expect(result).toEqual({
@@ -267,7 +272,13 @@ describe("Pages Translation Handler", () => {
267
272
  settings: page.settings,
268
273
  widgets: page.widgets,
269
274
  categories: ["cat1"],
270
- translations: {},
275
+ translations: {
276
+ fr: {
277
+ id: "translated-page-fr",
278
+ slug: page.slug,
279
+ visibility: page.visibility,
280
+ },
281
+ },
271
282
  languages: ["fr"],
272
283
  },
273
284
  blueprint: page,
@@ -0,0 +1,169 @@
1
+ import AuthHelper from "../../helpers/AuthHelper";
2
+
3
+ describe("AuthHelper", () => {
4
+ describe("parseJWT", () => {
5
+ test("parses JWT token correctly", () => {
6
+ const payload = { user: "test", exp: 1735689600 };
7
+ const encodedPayload = Buffer.from(JSON.stringify(payload)).toString(
8
+ "base64"
9
+ );
10
+ const mockToken = `header.${encodedPayload}.signature`;
11
+
12
+ const result = AuthHelper.parseJWT(mockToken);
13
+ expect(result).toEqual(payload);
14
+ });
15
+ });
16
+
17
+ describe("verifyJWT", () => {
18
+ test("returns true for valid non-expired token", () => {
19
+ const futureTime = Math.floor(Date.now() / 1000) + 3600;
20
+ const payload = { exp: futureTime };
21
+ const encodedPayload = Buffer.from(JSON.stringify(payload)).toString(
22
+ "base64"
23
+ );
24
+ const mockToken = `header.${encodedPayload}.signature`;
25
+
26
+ const result = AuthHelper.verifyJWT(mockToken);
27
+ expect(result).toBe(true);
28
+ });
29
+
30
+ test("returns false for expired token", () => {
31
+ const pastTime = Math.floor(Date.now() / 1000) - 3600;
32
+ const payload = { exp: pastTime };
33
+ const encodedPayload = Buffer.from(JSON.stringify(payload)).toString(
34
+ "base64"
35
+ );
36
+ const mockToken = `header.${encodedPayload}.signature`;
37
+
38
+ const result = AuthHelper.verifyJWT(mockToken);
39
+ expect(result).toBe(false);
40
+ });
41
+
42
+ test("returns false for token without exp", () => {
43
+ const payload = { user: "test" };
44
+ const encodedPayload = Buffer.from(JSON.stringify(payload)).toString(
45
+ "base64"
46
+ );
47
+ const mockToken = `header.${encodedPayload}.signature`;
48
+
49
+ const result = AuthHelper.verifyJWT(mockToken);
50
+ expect(result).toBe(false);
51
+ });
52
+ });
53
+
54
+ describe("isAuthenticated", () => {
55
+ test("returns true for allowed paths", () => {
56
+ expect(AuthHelper.isAuthenticated({ path: "/login" })).toBe(true);
57
+ expect(
58
+ AuthHelper.isAuthenticated({ path: "/auth/google/callback" })
59
+ ).toBe(true);
60
+ expect(AuthHelper.isAuthenticated({ path: "/remote/verify" })).toBe(true);
61
+ });
62
+
63
+ test("returns true for valid token", () => {
64
+ const futureTime = Math.floor(Date.now() / 1000) + 3600;
65
+ const payload = { exp: futureTime };
66
+ const encodedPayload = Buffer.from(JSON.stringify(payload)).toString(
67
+ "base64"
68
+ );
69
+ const mockToken = `header.${encodedPayload}.signature`;
70
+
71
+ expect(
72
+ AuthHelper.isAuthenticated({
73
+ path: "/protected",
74
+ token: mockToken,
75
+ })
76
+ ).toBe(true);
77
+ });
78
+
79
+ test("returns false for invalid token", () => {
80
+ const pastTime = Math.floor(Date.now() / 1000) - 3600;
81
+ const payload = { exp: pastTime };
82
+ const encodedPayload = Buffer.from(JSON.stringify(payload)).toString(
83
+ "base64"
84
+ );
85
+ const mockToken = `header.${encodedPayload}.signature`;
86
+
87
+ expect(
88
+ AuthHelper.isAuthenticated({
89
+ path: "/protected",
90
+ token: mockToken,
91
+ })
92
+ ).toBe(false);
93
+ });
94
+
95
+ test("returns false for protected path without token", () => {
96
+ expect(
97
+ AuthHelper.isAuthenticated({
98
+ path: "/protected",
99
+ })
100
+ ).toBe(false);
101
+ });
102
+ });
103
+
104
+ describe("getGoogleURL", () => {
105
+ test("returns correct URL with valid params", () => {
106
+ const params = {
107
+ gateway_url: "https://gateway.example.com",
108
+ gateway_project: "project1",
109
+ gateway_user_pool: "pool1",
110
+ auth_callback_url: "https://app.example.com/callback",
111
+ };
112
+
113
+ const expectedURL =
114
+ "https://gateway.example.com/users/auth/project1/pool1/google?return_to=https://app.example.com/callback";
115
+
116
+ expect(AuthHelper.getGoogleURL(params)).toBe(expectedURL);
117
+ });
118
+
119
+ test("throws error when any of the required params is missing or blank - auth_callback_url", () => {
120
+ const params = {
121
+ gateway_url: "https://gateway.example.com",
122
+ gateway_project: "project1",
123
+ gateway_user_pool: "pool1",
124
+ auth_callback_url: "",
125
+ };
126
+
127
+ expect(() => {
128
+ AuthHelper.getGoogleURL(params);
129
+ }).toThrow("Invalid ENVs for authentication configuration");
130
+ });
131
+
132
+ test("throws error when any of the required params is missing or blank - gateway_user_pool", () => {
133
+ const params = {
134
+ gateway_url: "https://gateway.example.com",
135
+ gateway_project: "project1",
136
+ auth_callback_url: "https://app.example.com/callback",
137
+ };
138
+
139
+ expect(() => {
140
+ AuthHelper.getGoogleURL(params);
141
+ }).toThrow("Invalid ENVs for authentication configuration");
142
+ });
143
+
144
+ test("throws error when any of the required params is missing or blank - gateway_project", () => {
145
+ const params = {
146
+ gateway_url: "https://gateway.example.com",
147
+ gateway_project: "",
148
+ gateway_user_pool: "pool1",
149
+ auth_callback_url: "https://app.example.com/callback",
150
+ };
151
+
152
+ expect(() => {
153
+ AuthHelper.getGoogleURL(params);
154
+ }).toThrow("Invalid ENVs for authentication configuration");
155
+ });
156
+
157
+ test("throws error when any of the required params is missing or blank - gateway_url", () => {
158
+ const params = {
159
+ gateway_project: "project1",
160
+ gateway_user_pool: "pool1",
161
+ auth_callback_url: "https://app.example.com/callback",
162
+ };
163
+
164
+ expect(() => {
165
+ AuthHelper.getGoogleURL(params);
166
+ }).toThrow("Invalid ENVs for authentication configuration");
167
+ });
168
+ });
169
+ });