@raketa-cloud/admin 0.0.88 → 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.
- package/jest.config.js +22 -0
- package/package.json +2 -2
- package/src/cloud/ArticleRevisions.js +33 -5
- package/src/cloud/Articles.js +34 -4
- package/src/cloud/EventRevisions.js +33 -5
- package/src/cloud/Events.js +35 -5
- package/src/cloud/PageRevisions.js +31 -3
- package/src/cloud/Pages.js +34 -4
- package/src/cloud/documents/articlesDocuments.js +44 -3
- package/src/cloud/documents/eventsDocuments.js +43 -2
- package/src/cloud/documents/pagesDocuments.js +43 -2
- package/src/handlers/articles/TranslationsHandler.js +81 -15
- package/src/handlers/events/TranslationsHandler.js +80 -14
- package/src/handlers/pages/TranslationsHandler.js +84 -18
- package/src/helpers/AuthHelper.js +2 -2
- package/src/tests/handlers/TagsHandler.test/create.test.js +96 -0
- package/src/tests/handlers/TagsHandler.test/create_form.test.js +25 -0
- package/src/tests/handlers/TagsHandler.test/destroy.test.js +51 -0
- package/src/tests/handlers/TagsHandler.test/edit_form.test.js +60 -0
- package/src/tests/handlers/TagsHandler.test/index.test.js +37 -0
- package/src/tests/handlers/TagsHandler.test/update.test.js +105 -0
- package/src/tests/handlers/articles/TranslationHandler.test/create.test.js +85 -79
- package/src/tests/handlers/events/TranslationHandler.test/create.test.js +84 -78
- package/src/tests/handlers/pages/TranslationHandler.test/create.test.js +83 -72
- package/src/tests/helpers/AuthHelper.test.js +169 -0
- package/src/tests/helpers/FormBuildert.test.js +357 -0
- package/src/tests/helpers/Inflector.test.js +75 -0
- package/src/views/articles/translations/create_form.ejs +2 -0
- package/src/views/events/translations/create_form.ejs +2 -0
- 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
|
|
42
|
-
.spyOn(ctx.client.pages, "
|
|
43
|
-
.mockImplementation(() =>
|
|
44
|
-
|
|
45
|
-
|
|
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(
|
|
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
|
|
95
|
-
.spyOn(ctx.client.pages, "
|
|
96
|
-
.mockImplementation((parentId,
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
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(
|
|
132
|
-
|
|
133
|
-
|
|
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
|
|
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
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
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
|
|
240
|
-
.spyOn(ctx.client.pages, "
|
|
241
|
-
.mockImplementation(() =>
|
|
242
|
-
|
|
243
|
-
|
|
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
|
|
248
|
-
.spyOn(ctx.client.pageRevisions, "
|
|
249
|
-
.mockImplementation(() =>
|
|
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(
|
|
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
|
+
});
|