@friggframework/schemas 2.0.0-next.78 → 2.0.0-next.80

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.
@@ -0,0 +1,311 @@
1
+ /**
2
+ * @file Authorization Mocks Tests
3
+ * @description Validates that all mock data generators produce schema-compliant data
4
+ */
5
+
6
+ const {
7
+ validateAuthorizationRequirements,
8
+ validateAuthorizationResponse,
9
+ validateAuthorizationSession,
10
+ } = require('../../index');
11
+
12
+ const {
13
+ createOAuth2Requirements,
14
+ createFormRequirements,
15
+ createOTPMultiStepFlow,
16
+ createAuthorizationSuccess,
17
+ createAuthorizationNextStep,
18
+ createAuthorizationSession,
19
+ createNagarisOTPFlowMock,
20
+ createOAuth2FlowMock,
21
+ } = require('../authorization-mocks');
22
+
23
+ describe('Authorization Mocks Schema Validation', () => {
24
+ describe('createOAuth2Requirements', () => {
25
+ it('should create valid OAuth2 requirements', () => {
26
+ const requirements = createOAuth2Requirements('hubspot');
27
+ const result = validateAuthorizationRequirements(requirements);
28
+
29
+ expect(result.valid).toBe(true);
30
+ expect(result.errors).toBeNull();
31
+ expect(requirements.type).toBe('oauth2');
32
+ expect(requirements.data.url).toContain('hubspot');
33
+ });
34
+
35
+ it('should support custom scopes', () => {
36
+ const requirements = createOAuth2Requirements('salesforce', {
37
+ scopes: ['full', 'refresh_token'],
38
+ });
39
+ const result = validateAuthorizationRequirements(requirements);
40
+
41
+ expect(result.valid).toBe(true);
42
+ expect(requirements.data.scopes).toEqual(['full', 'refresh_token']);
43
+ });
44
+
45
+ it('should support multi-step OAuth', () => {
46
+ const sessionId = 'session-123';
47
+ const requirements = createOAuth2Requirements('hubspot', {
48
+ isMultiStep: true,
49
+ step: 2,
50
+ totalSteps: 3,
51
+ sessionId,
52
+ });
53
+ const result = validateAuthorizationRequirements(requirements);
54
+
55
+ expect(result.valid).toBe(true);
56
+ expect(requirements.isMultiStep).toBe(true);
57
+ expect(requirements.step).toBe(2);
58
+ expect(requirements.sessionId).toBe(sessionId);
59
+ });
60
+ });
61
+
62
+ describe('createFormRequirements', () => {
63
+ it('should create valid form requirements with email/password', () => {
64
+ const requirements = createFormRequirements('custom-api', {
65
+ fields: ['email', 'password'],
66
+ });
67
+ const result = validateAuthorizationRequirements(requirements);
68
+
69
+ expect(result.valid).toBe(true);
70
+ expect(requirements.type).toBe('form');
71
+ expect(requirements.data.jsonSchema.properties).toHaveProperty('email');
72
+ expect(requirements.data.jsonSchema.properties).toHaveProperty('password');
73
+ expect(requirements.data.jsonSchema.required).toContain('email');
74
+ expect(requirements.data.jsonSchema.required).toContain('password');
75
+ });
76
+
77
+ it('should create valid OTP field', () => {
78
+ const requirements = createFormRequirements('nagaris', {
79
+ fields: ['otp'],
80
+ });
81
+ const result = validateAuthorizationRequirements(requirements);
82
+
83
+ expect(result.valid).toBe(true);
84
+ expect(requirements.data.jsonSchema.properties.otp).toBeDefined();
85
+ expect(requirements.data.jsonSchema.properties.otp.pattern).toBe('^[0-9]{6}$');
86
+ expect(requirements.data.uiSchema.otp['ui:help']).toContain('6-digit');
87
+ });
88
+
89
+ it('should support API key fields', () => {
90
+ const requirements = createFormRequirements('api-service', {
91
+ fields: ['api_key'],
92
+ });
93
+ const result = validateAuthorizationRequirements(requirements);
94
+
95
+ expect(result.valid).toBe(true);
96
+ expect(requirements.data.jsonSchema.properties).toHaveProperty('api_key');
97
+ });
98
+ });
99
+
100
+ describe('createOTPMultiStepFlow', () => {
101
+ it('should create valid multi-step flow', () => {
102
+ const flow = createOTPMultiStepFlow('nagaris');
103
+
104
+ // Validate step 1
105
+ const step1Result = validateAuthorizationRequirements(flow.step1);
106
+ expect(step1Result.valid).toBe(true);
107
+ expect(flow.step1.step).toBe(1);
108
+ expect(flow.step1.totalSteps).toBe(2);
109
+ expect(flow.step1.isMultiStep).toBe(true);
110
+
111
+ // Validate step 2
112
+ const step2 = flow.step2('session-abc');
113
+ const step2Result = validateAuthorizationRequirements(step2);
114
+ expect(step2Result.valid).toBe(true);
115
+ expect(step2.step).toBe(2);
116
+ expect(step2.sessionId).toBe('session-abc');
117
+ });
118
+ });
119
+
120
+ describe('createAuthorizationSuccess', () => {
121
+ it('should create valid success response', () => {
122
+ const success = createAuthorizationSuccess('hubspot');
123
+ const result = validateAuthorizationResponse(success);
124
+
125
+ expect(result.valid).toBe(true);
126
+ expect(success.entity_id).toBeDefined();
127
+ expect(success.credential_id).toBeDefined();
128
+ expect(success.type).toBe('hubspot');
129
+ expect(success.display).toContain('hubspot');
130
+ });
131
+
132
+ it('should support custom IDs', () => {
133
+ const success = createAuthorizationSuccess('salesforce', {
134
+ entityId: 'entity-123',
135
+ credentialId: 'cred-456',
136
+ display: 'My Salesforce Org',
137
+ });
138
+ const result = validateAuthorizationResponse(success);
139
+
140
+ expect(result.valid).toBe(true);
141
+ expect(success.entity_id).toBe('entity-123');
142
+ expect(success.credential_id).toBe('cred-456');
143
+ expect(success.display).toBe('My Salesforce Org');
144
+ });
145
+ });
146
+
147
+ describe('createAuthorizationNextStep', () => {
148
+ it('should create valid next step response', () => {
149
+ const step2Reqs = createFormRequirements('nagaris', {
150
+ fields: ['otp'],
151
+ step: 2,
152
+ totalSteps: 2,
153
+ });
154
+
155
+ const nextStep = createAuthorizationNextStep(2, step2Reqs);
156
+ const result = validateAuthorizationResponse(nextStep);
157
+
158
+ expect(result.valid).toBe(true);
159
+ expect(nextStep.nextStep).toBe(2);
160
+ expect(nextStep.sessionId).toBeDefined();
161
+ expect(nextStep.requirements).toEqual(step2Reqs);
162
+ expect(nextStep.message).toContain('step');
163
+ });
164
+
165
+ it('should support custom session ID and message', () => {
166
+ const step2Reqs = createFormRequirements('nagaris', { fields: ['otp'] });
167
+ const nextStep = createAuthorizationNextStep(2, step2Reqs, {
168
+ sessionId: 'custom-session',
169
+ message: 'OTP sent to your email',
170
+ });
171
+ const result = validateAuthorizationResponse(nextStep);
172
+
173
+ expect(result.valid).toBe(true);
174
+ expect(nextStep.sessionId).toBe('custom-session');
175
+ expect(nextStep.message).toBe('OTP sent to your email');
176
+ });
177
+ });
178
+
179
+ describe('createAuthorizationSession', () => {
180
+ it('should create valid authorization session', () => {
181
+ const session = createAuthorizationSession('user-123', 'nagaris');
182
+ const result = validateAuthorizationSession(session);
183
+
184
+ expect(result.valid).toBe(true);
185
+ expect(session.userId).toBe('user-123');
186
+ expect(session.entityType).toBe('nagaris');
187
+ expect(session.sessionId).toBeDefined();
188
+ expect(session.currentStep).toBe(1);
189
+ expect(session.maxSteps).toBe(2);
190
+ expect(session.completed).toBe(false);
191
+ });
192
+
193
+ it('should support custom step data and completion', () => {
194
+ const session = createAuthorizationSession('user-456', 'hubspot', {
195
+ currentStep: 2,
196
+ maxSteps: 3,
197
+ stepData: { email: 'test@example.com', domain: 'mycompany' },
198
+ completed: true,
199
+ });
200
+ const result = validateAuthorizationSession(session);
201
+
202
+ expect(result.valid).toBe(true);
203
+ expect(session.currentStep).toBe(2);
204
+ expect(session.maxSteps).toBe(3);
205
+ expect(session.stepData.email).toBe('test@example.com');
206
+ expect(session.completed).toBe(true);
207
+ });
208
+
209
+ it('should have valid expiration timestamp', () => {
210
+ const session = createAuthorizationSession('user-789', 'salesforce', {
211
+ expiresInMinutes: 30,
212
+ });
213
+
214
+ const now = new Date();
215
+ const expiresAt = new Date(session.expiresAt);
216
+ const diffMinutes = (expiresAt - now) / (60 * 1000);
217
+
218
+ expect(diffMinutes).toBeGreaterThanOrEqual(29);
219
+ expect(diffMinutes).toBeLessThanOrEqual(31);
220
+ });
221
+ });
222
+
223
+ describe('createNagarisOTPFlowMock', () => {
224
+ it('should create complete valid Nagaris OTP flow', () => {
225
+ const flow = createNagarisOTPFlowMock('user-123');
226
+
227
+ // Step 1: Get requirements
228
+ const step1Reqs = flow.getStep1Requirements();
229
+ const step1ReqsResult = validateAuthorizationRequirements(step1Reqs);
230
+ expect(step1ReqsResult.valid).toBe(true);
231
+ expect(step1Reqs.step).toBe(1);
232
+
233
+ // Step 1: Submit
234
+ const step1Response = flow.submitStep1({ email: flow.email });
235
+ const step1ResponseResult = validateAuthorizationResponse(step1Response);
236
+ expect(step1ResponseResult.valid).toBe(true);
237
+ expect(step1Response.nextStep).toBe(2);
238
+ expect(step1Response.sessionId).toBe(flow.sessionId);
239
+
240
+ // Step 2: Submit
241
+ const step2Response = flow.submitStep2({ otp: '123456' });
242
+ const step2ResponseResult = validateAuthorizationResponse(step2Response);
243
+ expect(step2ResponseResult.valid).toBe(true);
244
+ expect(step2Response.entity_id).toBeDefined();
245
+ expect(step2Response.type).toBe('nagaris');
246
+
247
+ // Session
248
+ const sessionResult = validateAuthorizationSession(flow.session);
249
+ expect(sessionResult.valid).toBe(true);
250
+ expect(flow.session.userId).toBe('user-123');
251
+ expect(flow.session.entityType).toBe('nagaris');
252
+ });
253
+ });
254
+
255
+ describe('createOAuth2FlowMock', () => {
256
+ it('should create complete valid OAuth2 flow', () => {
257
+ const flow = createOAuth2FlowMock('hubspot', 'user-456');
258
+
259
+ // Get requirements
260
+ const requirements = flow.getRequirements();
261
+ const reqsResult = validateAuthorizationRequirements(requirements);
262
+ expect(reqsResult.valid).toBe(true);
263
+ expect(requirements.type).toBe('oauth2');
264
+ expect(requirements.isMultiStep).toBe(false);
265
+
266
+ // Handle callback
267
+ const callbackResponse = flow.handleCallback('auth-code-123', 'state-xyz');
268
+ const callbackResult = validateAuthorizationResponse(callbackResponse);
269
+ expect(callbackResult.valid).toBe(true);
270
+ expect(callbackResponse.entity_id).toBeDefined();
271
+ expect(callbackResponse.type).toBe('hubspot');
272
+
273
+ // No session for single-step OAuth
274
+ expect(flow.session).toBeNull();
275
+ });
276
+ });
277
+ });
278
+
279
+ describe('Cross-Package Compatibility', () => {
280
+ it('should work with @friggframework/core integration tests', () => {
281
+ // This mock can be used in core integration tests
282
+ const flow = createNagarisOTPFlowMock('test-user');
283
+ const session = flow.session;
284
+
285
+ // Core would validate session before storing
286
+ const result = validateAuthorizationSession(session);
287
+ expect(result.valid).toBe(true);
288
+ });
289
+
290
+ it('should work with @friggframework/ui component tests', () => {
291
+ // This mock can be used in UI component tests
292
+ const requirements = createFormRequirements('nagaris', {
293
+ fields: ['email'],
294
+ });
295
+
296
+ // UI would render form based on jsonSchema
297
+ expect(requirements.data.jsonSchema.properties.email).toBeDefined();
298
+ expect(requirements.data.uiSchema.email).toBeDefined();
299
+ });
300
+
301
+ it('should work with management-ui tests', () => {
302
+ // Management UI would display auth flows
303
+ const oauthReqs = createOAuth2Requirements('hubspot');
304
+ const formReqs = createFormRequirements('api-service', {
305
+ fields: ['api_key'],
306
+ });
307
+
308
+ expect(oauthReqs.type).toBe('oauth2');
309
+ expect(formReqs.type).toBe('form');
310
+ });
311
+ });
@@ -0,0 +1,339 @@
1
+ /**
2
+ * @file Authorization Mock Data Generators
3
+ * @description Canonical mock data for authorization flows
4
+ *
5
+ * These mocks are schema-compliant and can be used across:
6
+ * - @friggframework/core tests
7
+ * - @friggframework/ui tests
8
+ * - @friggframework/devtools/management-ui tests
9
+ * - Integration tests
10
+ *
11
+ * Usage:
12
+ * ```js
13
+ * const { createOAuth2Requirements, createFormRequirements } = require('@friggframework/schemas/mocks/authorization-mocks');
14
+ *
15
+ * const mockData = createOAuth2Requirements('hubspot');
16
+ * ```
17
+ */
18
+
19
+ const crypto = require('crypto');
20
+
21
+ /**
22
+ * Generate a unique session ID
23
+ */
24
+ function generateSessionId() {
25
+ return crypto.randomUUID();
26
+ }
27
+
28
+ /**
29
+ * Create OAuth2 authorization requirements
30
+ * @param {string} moduleType - Module type (e.g., 'hubspot', 'salesforce')
31
+ * @param {object} options - Additional options
32
+ * @returns {object} OAuth2 requirements object
33
+ */
34
+ function createOAuth2Requirements(moduleType, options = {}) {
35
+ const {
36
+ scopes = ['read', 'write'],
37
+ isMultiStep = false,
38
+ step = 1,
39
+ totalSteps = 1,
40
+ sessionId = null
41
+ } = options;
42
+
43
+ const requirements = {
44
+ type: 'oauth2',
45
+ step,
46
+ totalSteps,
47
+ isMultiStep,
48
+ data: {
49
+ url: `https://auth.${moduleType}.com/oauth/authorize?client_id=abc123&redirect_uri=http://localhost:3000/callback&state=xyz`,
50
+ scopes
51
+ }
52
+ };
53
+
54
+ if (sessionId) {
55
+ requirements.sessionId = sessionId;
56
+ }
57
+
58
+ return requirements;
59
+ }
60
+
61
+ /**
62
+ * Create form-based authorization requirements
63
+ * @param {string} moduleType - Module type
64
+ * @param {object} options - Additional options
65
+ * @returns {object} Form requirements object
66
+ */
67
+ function createFormRequirements(moduleType, options = {}) {
68
+ const {
69
+ fields = ['email', 'password'],
70
+ isMultiStep = false,
71
+ step = 1,
72
+ totalSteps = 1,
73
+ sessionId = null
74
+ } = options;
75
+
76
+ const properties = {};
77
+ const required = [];
78
+
79
+ // Build JSON schema properties
80
+ fields.forEach(field => {
81
+ if (field === 'email') {
82
+ properties.email = {
83
+ type: 'string',
84
+ format: 'email',
85
+ title: 'Email Address'
86
+ };
87
+ required.push('email');
88
+ } else if (field === 'password') {
89
+ properties.password = {
90
+ type: 'string',
91
+ title: 'Password',
92
+ minLength: 6
93
+ };
94
+ required.push('password');
95
+ } else if (field === 'api_key') {
96
+ properties.api_key = {
97
+ type: 'string',
98
+ title: 'API Key'
99
+ };
100
+ required.push('api_key');
101
+ } else if (field === 'otp') {
102
+ properties.otp = {
103
+ type: 'string',
104
+ title: 'One-Time Password',
105
+ pattern: '^[0-9]{6}$'
106
+ };
107
+ required.push('otp');
108
+ } else {
109
+ properties[field] = {
110
+ type: 'string',
111
+ title: field.charAt(0).toUpperCase() + field.slice(1)
112
+ };
113
+ }
114
+ });
115
+
116
+ const requirements = {
117
+ type: 'form',
118
+ step,
119
+ totalSteps,
120
+ isMultiStep,
121
+ data: {
122
+ jsonSchema: {
123
+ title: `Connect ${moduleType}`,
124
+ description: `Enter your ${moduleType} credentials`,
125
+ type: 'object',
126
+ required,
127
+ properties
128
+ },
129
+ uiSchema: {
130
+ email: {
131
+ 'ui:placeholder': 'your.email@company.com'
132
+ },
133
+ password: {
134
+ 'ui:widget': 'password'
135
+ },
136
+ otp: {
137
+ 'ui:placeholder': '123456',
138
+ 'ui:help': 'Enter the 6-digit code sent to your email'
139
+ }
140
+ }
141
+ }
142
+ };
143
+
144
+ if (sessionId) {
145
+ requirements.sessionId = sessionId;
146
+ }
147
+
148
+ return requirements;
149
+ }
150
+
151
+ /**
152
+ * Create multi-step OTP authorization flow (like Nagaris)
153
+ * @param {string} moduleType - Module type
154
+ * @returns {object} Multi-step requirements
155
+ */
156
+ function createOTPMultiStepFlow(moduleType = 'nagaris') {
157
+ return {
158
+ step1: createFormRequirements(moduleType, {
159
+ fields: ['email'],
160
+ isMultiStep: true,
161
+ step: 1,
162
+ totalSteps: 2
163
+ }),
164
+ step2: (sessionId) => createFormRequirements(moduleType, {
165
+ fields: ['otp'],
166
+ isMultiStep: true,
167
+ step: 2,
168
+ totalSteps: 2,
169
+ sessionId
170
+ })
171
+ };
172
+ }
173
+
174
+ /**
175
+ * Create authorization success response
176
+ * @param {string} moduleType - Module type
177
+ * @param {object} options - Additional options
178
+ * @returns {object} Authorization success response
179
+ */
180
+ function createAuthorizationSuccess(moduleType, options = {}) {
181
+ const {
182
+ entityId = crypto.randomUUID(),
183
+ credentialId = crypto.randomUUID(),
184
+ display = `My ${moduleType} Account`
185
+ } = options;
186
+
187
+ return {
188
+ entity_id: entityId,
189
+ credential_id: credentialId,
190
+ type: moduleType,
191
+ display
192
+ };
193
+ }
194
+
195
+ /**
196
+ * Create authorization next step response
197
+ * @param {number} nextStep - Next step number
198
+ * @param {object} requirements - Requirements for next step
199
+ * @param {object} options - Additional options
200
+ * @returns {object} Next step response
201
+ */
202
+ function createAuthorizationNextStep(nextStep, requirements, options = {}) {
203
+ const {
204
+ sessionId = generateSessionId(),
205
+ message = `Step ${nextStep - 1} completed. Proceed to step ${nextStep}.`
206
+ } = options;
207
+
208
+ return {
209
+ nextStep,
210
+ sessionId,
211
+ requirements,
212
+ message
213
+ };
214
+ }
215
+
216
+ /**
217
+ * Create authorization session object
218
+ * @param {string} userId - User ID
219
+ * @param {string} entityType - Entity/module type
220
+ * @param {object} options - Additional options
221
+ * @returns {object} Authorization session
222
+ */
223
+ function createAuthorizationSession(userId, entityType, options = {}) {
224
+ const {
225
+ currentStep = 1,
226
+ maxSteps = 2,
227
+ stepData = {},
228
+ expiresInMinutes = 15,
229
+ completed = false
230
+ } = options;
231
+
232
+ const now = new Date();
233
+ const expiresAt = new Date(now.getTime() + expiresInMinutes * 60000);
234
+
235
+ return {
236
+ sessionId: generateSessionId(),
237
+ userId,
238
+ entityType,
239
+ currentStep,
240
+ maxSteps,
241
+ stepData,
242
+ expiresAt: expiresAt.toISOString(),
243
+ completed,
244
+ createdAt: now.toISOString(),
245
+ updatedAt: now.toISOString()
246
+ };
247
+ }
248
+
249
+ /**
250
+ * Create a complete Nagaris OTP flow mock
251
+ * @param {string} userId - User ID
252
+ * @returns {object} Complete flow mock with step functions
253
+ */
254
+ function createNagarisOTPFlowMock(userId = 'user123') {
255
+ const sessionId = generateSessionId();
256
+ const email = 'test@example.com';
257
+
258
+ return {
259
+ // Step 1: Get requirements (email)
260
+ getStep1Requirements: () => createFormRequirements('nagaris', {
261
+ fields: ['email'],
262
+ isMultiStep: true,
263
+ step: 1,
264
+ totalSteps: 2
265
+ }),
266
+
267
+ // Step 1: Submit email, get next step
268
+ submitStep1: (emailData) => {
269
+ const step2Reqs = createFormRequirements('nagaris', {
270
+ fields: ['otp'],
271
+ isMultiStep: true,
272
+ step: 2,
273
+ totalSteps: 2,
274
+ sessionId
275
+ });
276
+
277
+ return createAuthorizationNextStep(2, step2Reqs, {
278
+ sessionId,
279
+ message: 'OTP sent to your email. Please check your inbox.'
280
+ });
281
+ },
282
+
283
+ // Step 2: Submit OTP, get success
284
+ submitStep2: (otpData) => {
285
+ return createAuthorizationSuccess('nagaris', {
286
+ display: `Nagaris Account (${email})`
287
+ });
288
+ },
289
+
290
+ // Session object
291
+ session: createAuthorizationSession(userId, 'nagaris', {
292
+ currentStep: 1,
293
+ maxSteps: 2,
294
+ stepData: { email }
295
+ }),
296
+
297
+ // Utility
298
+ sessionId,
299
+ email
300
+ };
301
+ }
302
+
303
+ /**
304
+ * Create a complete OAuth2 flow mock
305
+ * @param {string} moduleType - Module type (e.g., 'hubspot')
306
+ * @param {string} userId - User ID
307
+ * @returns {object} Complete OAuth flow mock
308
+ */
309
+ function createOAuth2FlowMock(moduleType, userId = 'user123') {
310
+ return {
311
+ // Get initial requirements
312
+ getRequirements: () => createOAuth2Requirements(moduleType),
313
+
314
+ // OAuth callback with code
315
+ handleCallback: (code, state) => {
316
+ return createAuthorizationSuccess(moduleType, {
317
+ display: `My ${moduleType} Account`
318
+ });
319
+ },
320
+
321
+ // Session (single-step OAuth doesn't need session)
322
+ session: null
323
+ };
324
+ }
325
+
326
+ module.exports = {
327
+ // Generators
328
+ generateSessionId,
329
+ createOAuth2Requirements,
330
+ createFormRequirements,
331
+ createOTPMultiStepFlow,
332
+ createAuthorizationSuccess,
333
+ createAuthorizationNextStep,
334
+ createAuthorizationSession,
335
+
336
+ // Complete flow mocks
337
+ createNagarisOTPFlowMock,
338
+ createOAuth2FlowMock,
339
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@friggframework/schemas",
3
- "version": "2.0.0-next.78",
3
+ "version": "2.0.0-next.80",
4
4
  "description": "Canonical JSON Schema definitions for Frigg Framework",
5
5
  "main": "index.js",
6
6
  "author": "",
@@ -37,7 +37,9 @@
37
37
  "files": [
38
38
  "schemas/",
39
39
  "validators/",
40
+ "mocks/",
41
+ "middleware/",
40
42
  "index.js"
41
43
  ],
42
- "gitHead": "c8e0c0c725a9e49b76d4e095927a443640bf9ec9"
44
+ "gitHead": "7a66dfcb02143941411ff26aaee866afc1473df8"
43
45
  }