@friggframework/core 2.0.0--canary.396.6862738.0 → 2.0.0--canary.397.c07f148.0

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 (69) hide show
  1. package/README.md +931 -50
  2. package/core/create-handler.js +1 -0
  3. package/credential/credential-repository.js +48 -1
  4. package/credential/use-cases/update-authentication-status.js +15 -0
  5. package/handlers/backend-utils.js +34 -31
  6. package/handlers/routers/auth.js +1 -15
  7. package/index.js +1 -5
  8. package/integrations/integration-base.js +133 -40
  9. package/integrations/integration-repository.js +39 -3
  10. package/integrations/integration-router.js +109 -85
  11. package/integrations/tests/doubles/dummy-integration-class.js +90 -0
  12. package/integrations/tests/doubles/test-integration-repository.js +89 -0
  13. package/integrations/tests/use-cases/create-integration.test.js +124 -0
  14. package/integrations/tests/use-cases/delete-integration-for-user.test.js +143 -0
  15. package/integrations/tests/use-cases/get-integration-for-user.test.js +143 -0
  16. package/integrations/tests/use-cases/get-integration-instance.test.js +169 -0
  17. package/integrations/tests/use-cases/get-integrations-for-user.test.js +169 -0
  18. package/integrations/tests/use-cases/get-possible-integrations.test.js +188 -0
  19. package/integrations/tests/use-cases/update-integration-messages.test.js +142 -0
  20. package/integrations/tests/use-cases/update-integration-status.test.js +103 -0
  21. package/integrations/tests/use-cases/update-integration.test.js +134 -0
  22. package/integrations/use-cases/create-integration.js +25 -12
  23. package/integrations/use-cases/delete-integration-for-user.js +21 -2
  24. package/integrations/use-cases/get-integration-for-user.js +28 -13
  25. package/integrations/use-cases/get-integration-instance-by-definition.js +67 -0
  26. package/integrations/use-cases/get-integration-instance.js +20 -11
  27. package/integrations/use-cases/get-integrations-for-user.js +22 -10
  28. package/integrations/use-cases/get-possible-integrations.js +27 -0
  29. package/integrations/use-cases/update-integration-messages.js +31 -0
  30. package/integrations/use-cases/update-integration-status.js +28 -0
  31. package/integrations/use-cases/update-integration.js +23 -13
  32. package/integrations/utils/map-integration-dto.js +0 -1
  33. package/{module-plugin → modules}/entity.js +1 -0
  34. package/{module-plugin → modules}/index.js +0 -4
  35. package/{module-plugin/module-service.js → modules/module-factory.js} +9 -5
  36. package/modules/module-repository.js +107 -0
  37. package/modules/module.js +218 -0
  38. package/modules/tests/doubles/test-module-factory.js +16 -0
  39. package/modules/tests/doubles/test-module-repository.js +19 -0
  40. package/{module-plugin → modules}/use-cases/get-entities-for-user.js +1 -1
  41. package/modules/use-cases/get-entity-options-by-id.js +58 -0
  42. package/modules/use-cases/get-entity-options-by-type.js +34 -0
  43. package/modules/use-cases/get-module-instance-from-type.js +31 -0
  44. package/modules/use-cases/get-module.js +56 -0
  45. package/modules/use-cases/process-authorization-callback.js +108 -0
  46. package/modules/use-cases/refresh-entity-options.js +58 -0
  47. package/modules/use-cases/test-module-auth.js +54 -0
  48. package/{module-plugin → modules}/utils/map-module-dto.js +1 -1
  49. package/package.json +5 -5
  50. package/syncs/sync.js +0 -1
  51. package/types/module-plugin/index.d.ts +0 -35
  52. package/types/syncs/index.d.ts +0 -2
  53. package/integrations/integration.js +0 -233
  54. package/integrations/test/integration-base.test.js +0 -144
  55. package/module-plugin/manager.js +0 -169
  56. package/module-plugin/module-factory.js +0 -42
  57. package/module-plugin/module-repository.js +0 -70
  58. package/module-plugin/module.js +0 -329
  59. /package/{module-plugin → modules}/ModuleConstants.js +0 -0
  60. /package/{module-plugin → modules}/credential.js +0 -0
  61. /package/{module-plugin → modules}/requester/api-key.js +0 -0
  62. /package/{module-plugin → modules}/requester/basic.js +0 -0
  63. /package/{module-plugin → modules}/requester/oauth-2.js +0 -0
  64. /package/{module-plugin → modules}/requester/requester.js +0 -0
  65. /package/{module-plugin → modules}/requester/requester.test.js +0 -0
  66. /package/{module-plugin → modules}/test/auther.test.js +0 -0
  67. /package/{module-plugin → modules}/test/mock-api/api.js +0 -0
  68. /package/{module-plugin → modules}/test/mock-api/definition.js +0 -0
  69. /package/{module-plugin → modules}/test/mock-api/mocks/hubspot.js +0 -0
@@ -0,0 +1,58 @@
1
+ const { Module } = require('../module');
2
+
3
+ class RefreshEntityOptions {
4
+ /**
5
+ * @param {Object} params
6
+ * @param {import('../module-repository').ModuleRepository} params.moduleRepository
7
+ * @param {} params.moduleDefinitions
8
+ */
9
+ constructor({ moduleRepository, moduleDefinitions }) {
10
+ this.moduleRepository = moduleRepository;
11
+ this.moduleDefinitions = moduleDefinitions;
12
+ }
13
+
14
+ /**
15
+ * Retrieve a Module instance for a given user and entity/module type.
16
+ * @param {string} userId
17
+ * @param {string} entityId
18
+ */
19
+ async execute(entityId, userId, options) {
20
+ const entity = await this.moduleRepository.findEntityById(
21
+ entityId,
22
+ userId
23
+ );
24
+
25
+ if (!entity) {
26
+ throw new Error(`Entity ${entityId} not found`);
27
+ }
28
+
29
+ if (entity.userId !== userId) {
30
+ throw new Error(
31
+ `Entity ${entityId} does not belong to user ${userId}`
32
+ );
33
+ }
34
+
35
+ const entityType = entity.type;
36
+ const moduleDefinition = this.moduleDefinitions.find((def) => {
37
+ const modelName = Module.getEntityModelFromDefinition(def).modelName;
38
+ return entityType === modelName;
39
+ });
40
+
41
+ if (!moduleDefinition) {
42
+ throw new Error(
43
+ `Module definition not found for entity type: ${entityType}`
44
+ );
45
+ }
46
+
47
+ const module = new Module({
48
+ userId,
49
+ entity,
50
+ definition: moduleDefinition,
51
+ });
52
+
53
+ await module.refreshEntityOptions(options);
54
+ return module.getEntityOptions();
55
+ }
56
+ }
57
+
58
+ module.exports = { RefreshEntityOptions };
@@ -0,0 +1,54 @@
1
+ const { Module } = require('../module');
2
+
3
+ class TestModuleAuth {
4
+ /**
5
+ * @param {Object} params - Configuration parameters.
6
+ * @param {import('./module-repository').ModuleRepository} params.moduleRepository - Repository for module data operations.
7
+ * @param {Array<Object>} params.moduleDefinitions - Array of module definitions.
8
+ */
9
+ constructor({ moduleRepository, moduleDefinitions }) {
10
+ this.moduleRepository = moduleRepository;
11
+ this.moduleDefinitions = moduleDefinitions;
12
+ }
13
+
14
+ async execute(entityId, userId) {
15
+ const entity = await this.moduleRepository.findEntityById(
16
+ entityId,
17
+ userId
18
+ );
19
+
20
+ if (!entity) {
21
+ throw new Error(`Entity ${entityId} not found`);
22
+ }
23
+
24
+ if (entity.userId !== userId) {
25
+ throw new Error(
26
+ `Entity ${entityId} does not belong to user ${userId}`
27
+ );
28
+ }
29
+
30
+ const entityType = entity.type;
31
+ const moduleDefinition = this.moduleDefinitions.find((def) => {
32
+ const modelName = Module.getEntityModelFromDefinition(def).modelName;
33
+ return entityType === modelName;
34
+ });
35
+
36
+ if (!moduleDefinition) {
37
+ throw new Error(
38
+ `Module definition not found for entity type: ${entityType}`
39
+ );
40
+ }
41
+
42
+ const module = new Module({
43
+ userId,
44
+ entity,
45
+ definition: moduleDefinition,
46
+ });
47
+
48
+ const testAuthResponse = await module.testAuth();
49
+
50
+ return testAuthResponse;
51
+ }
52
+ }
53
+
54
+ module.exports = { TestModuleAuth };
@@ -6,7 +6,7 @@ function mapModuleClassToModuleDTO(moduleInstance) {
6
6
  if (!moduleInstance) return null;
7
7
 
8
8
  return {
9
- id: moduleInstance.entity._id.toString(),
9
+ id: moduleInstance.entity.id,
10
10
  name: moduleInstance.name,
11
11
  userId: moduleInstance.userId,
12
12
  entity: moduleInstance.entity,
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@friggframework/core",
3
3
  "prettier": "@friggframework/prettier-config",
4
- "version": "2.0.0--canary.396.6862738.0",
4
+ "version": "2.0.0--canary.397.c07f148.0",
5
5
  "dependencies": {
6
6
  "@hapi/boom": "^10.0.1",
7
7
  "aws-sdk": "^2.1200.0",
@@ -22,9 +22,9 @@
22
22
  "uuid": "^9.0.1"
23
23
  },
24
24
  "devDependencies": {
25
- "@friggframework/eslint-config": "2.0.0--canary.396.6862738.0",
26
- "@friggframework/prettier-config": "2.0.0--canary.396.6862738.0",
27
- "@friggframework/test": "2.0.0--canary.396.6862738.0",
25
+ "@friggframework/eslint-config": "2.0.0--canary.397.c07f148.0",
26
+ "@friggframework/prettier-config": "2.0.0--canary.397.c07f148.0",
27
+ "@friggframework/test": "2.0.0--canary.397.c07f148.0",
28
28
  "@types/lodash": "4.17.15",
29
29
  "@typescript-eslint/eslint-plugin": "^8.0.0",
30
30
  "chai": "^4.3.6",
@@ -53,5 +53,5 @@
53
53
  },
54
54
  "homepage": "https://github.com/friggframework/frigg#readme",
55
55
  "description": "",
56
- "gitHead": "6862738f1b370d1bb37353809b30586267e7deb0"
56
+ "gitHead": "c07f148505b5831d3f8be581529be9f0e18b5934"
57
57
  }
package/syncs/sync.js CHANGED
@@ -1,5 +1,4 @@
1
1
  const md5 = require("md5");
2
- const ModuleManager = require('../module-plugin');
3
2
  const { debug } = require("packages/logs");
4
3
  const { get } = require("packages/assertions");
5
4
 
@@ -20,42 +20,7 @@ declare module "@friggframework/module-plugin" {
20
20
  }
21
21
 
22
22
  export type MappedEntity = Entity & { id: string; type: any };
23
- export class ModuleManager extends Delegate implements IFriggModuleManager {
24
- static Entity: Entity;
25
- static Credential: Credential;
26
23
 
27
- constructor(params: { userId: string });
28
-
29
- static getName(): any;
30
- static getInstance(params: any): Promise<any>;
31
- static getEntitiesForUserId(userId: string): Promise<MappedEntity[]>;
32
-
33
- batchCreateSyncObjects(syncObjects: any, syncManager: any): Promise<any>;
34
- batchUpdateSyncObjects(syncObjects: any, syncManager: any): Promise<any>;
35
- findOrCreateEntity(params: any): Promise<any>;
36
- getAllSyncObjects(SyncClass: any): Promise<any>;
37
- getAuthorizationRequirements(params: any): Promise<any>;
38
- getEntityId(): Promise<string>;
39
- getEntityOptions(): Promise<any>;
40
- markCredentialsInvalid(): Promise<Credential>;
41
- processAuthorizationCallback(params: any): Promise<any>;
42
- testAuth(params: any): Promise<any>;
43
- validateAuthorizationRequirements(): Promise<boolean>;
44
- }
45
-
46
- interface IFriggModuleManager extends IFriggDelegate {
47
- getEntityId(): Promise<string>;
48
- validateAuthorizationRequirements(): Promise<boolean>;
49
- getAuthorizationRequirements(params: any): Promise<any>;
50
- testAuth(params: any): Promise<any>;
51
- processAuthorizationCallback(params: any): Promise<any>;
52
- getEntityOptions(): Promise<any>;
53
- findOrCreateEntity(params: any): Promise<any>;
54
- getAllSyncObjects(SyncClass: any): Promise<any>;
55
- batchCreateSyncObjects(syncObjects: any, syncManager: any): Promise<any>;
56
- batchUpdateSyncObjects(syncObjects: any, syncManager: any): Promise<any>;
57
- markCredentialsInvalid(): Promise<Credential>;
58
- }
59
24
 
60
25
  export class Requester implements IFriggRequester {
61
26
  DLGT_INVALID_AUTH: string;
@@ -28,7 +28,6 @@ declare module "@friggframework/syncs/manager" {
28
28
  confirmCreate(
29
29
  syncObj: Sync,
30
30
  createdId: string,
31
- moduleManager: any
32
31
  ): Promise<any>;
33
32
  confirmUpdate(syncObj: Sync): Promise<any>;
34
33
  createSyncDBObject(objArr: any[], entities: any[]): Promise<any>;
@@ -50,7 +49,6 @@ declare module "@friggframework/syncs/manager" {
50
49
  confirmCreate(
51
50
  syncObj: Sync,
52
51
  createdId: string,
53
- moduleManager: any
54
52
  ): Promise<any>;
55
53
  confirmUpdate(syncObj: Sync): Promise<any>;
56
54
  }
@@ -1,233 +0,0 @@
1
- const { Options } = require('./options');
2
-
3
- /**
4
- * Integration (Domain Aggregate-Root)
5
- * ----------------------------------
6
- * This class represents a *configured* integration instance at runtime. It is
7
- * deliberately split into **two layers**:
8
- * 1. A *data snapshot* of the persisted record (id, userId, config, etc.).
9
- * 2. A *behaviour* object: a concrete class supplied by the app developer
10
- * that extends `IntegrationBase` and implements event handlers, user
11
- * actions, custom routes, etc.
12
- *
13
- * The two layers are glued together via a **JavaScript `Proxy`**. When a
14
- * property is requested on an `Integration` instance we:
15
- * • Check if the property exists on the wrapper itself (data-layer).
16
- * • Fallback to the behaviour instance (logic-layer).
17
- * • If the value is a function we `.bind(this)` so that the function's
18
- * `this` always points to the *wrapper* – giving it access to both data
19
- * and behaviour transparently.
20
- *
21
- * This means you can treat a hydrated Integration as if it *were* the custom
22
- * class:
23
- *
24
- * ```js
25
- * const integration = await getIntegration.execute(id, userId);
26
- * // `send` actually lives on IntegrationBase but is accessible here
27
- * const actions = await integration.send('GET_USER_ACTIONS');
28
- * ```
29
- *
30
- * A corollary benefit is that **circular references stay internal**: the heavy
31
- * `Module → Api → delegate` graph is never exposed when we later serialise the
32
- * object to JSON – we map it to a DTO first.
33
- */
34
-
35
- /**
36
- * Integration Domain Entity
37
- * Represents a configured integration with its data and behavior
38
- * Uses the strategy pattern to delegate behavior to the integration class
39
- * This is the main class that is used to interact with integrations
40
- */
41
- class Integration {
42
- constructor({
43
- id,
44
- userId,
45
- entities,
46
- config,
47
- status,
48
- version,
49
- messages,
50
- integrationClass,
51
- modules = {}
52
- }) {
53
- // Data from record
54
- this.id = id;
55
- this.userId = userId;
56
- this.entities = entities;
57
- this.config = config;
58
- this.status = status;
59
- this.version = version;
60
- this.messages = messages;
61
-
62
- // Integration behavior (strategy pattern)
63
- this.integrationClass = integrationClass;
64
-
65
- // Loaded modules
66
- this.modules = modules;
67
-
68
- // Initialize basic behavior (sync parts only)
69
- this._initializeBasicBehavior();
70
-
71
- // --- Behaviour delegation via Proxy --------------------------------
72
- // The Proxy merges the *data layer* (this wrapper) with the *behaviour
73
- // layer* (custom IntegrationBase subclass). Consumers don't have to
74
- // know (or care) where a method/property is defined.
75
- return new Proxy(this, {
76
- get(target, prop) {
77
- // First, check if property exists on Integration entity
78
- if (prop in target) {
79
- return target[prop];
80
- }
81
-
82
- // Then, check if it exists on the behavior instance
83
- if (target.behavior && prop in target.behavior) {
84
- const value = target.behavior[prop];
85
-
86
- // If it's a function, bind the context to the Integration entity
87
- if (typeof value === 'function') {
88
- return value.bind(target);
89
- }
90
-
91
- return value;
92
- }
93
-
94
- // Return undefined for non-existent properties
95
- return undefined;
96
- }
97
- });
98
- }
99
-
100
- _initializeBasicBehavior() {
101
- // Initialize basic behavior (sync parts only)
102
- if (this.integrationClass) {
103
- // Create instance for behavior delegation
104
- this.behavior = new this.integrationClass({
105
- userId: this.userId,
106
- integrationId: this.id
107
- });
108
-
109
- // Copy events
110
- this.events = this.behavior.events || {};
111
- this.defaultEvents = this.behavior.defaultEvents || {};
112
-
113
-
114
- // Expose behaviour instance methods directly on the wrapper so that
115
- // early-bound handlers (created before behaviour existed) can still
116
- // access them without falling back through the Proxy. This prevents
117
- // `undefined` errors for methods like `loadDynamicUserActions` that
118
- // may be invoked inside default event-handlers.
119
- const proto = Object.getPrototypeOf(this.behavior);
120
- for (const key of Object.getOwnPropertyNames(proto)) {
121
- if (key === 'constructor') continue;
122
- if (typeof proto[key] === 'function' && this[key] === undefined) {
123
- // Bind to behaviour so internal `this` remains correct.
124
- this[key] = proto[key].bind(this.behavior);
125
- }
126
- }
127
- }
128
- }
129
-
130
- async initialize() {
131
- // Complete async initialization
132
- if (this.behavior) {
133
- // Load dynamic user actions
134
- try {
135
- const additionalUserActions = await this.loadDynamicUserActions();
136
- this.events = { ...this.events, ...additionalUserActions };
137
- } catch (e) {
138
- this.addError(e);
139
- }
140
-
141
- // Register event handlers
142
- await this.registerEventHandlers();
143
- }
144
- }
145
-
146
- // Core methods that should always be on Integration entity
147
- // These override any behavior methods with the same name
148
-
149
- // Module access helpers
150
- getModule(key) {
151
- return this.modules[key];
152
- }
153
-
154
- setModule(key, module) {
155
- this.modules[key] = module;
156
- // Also set on behavior for backward compatibility
157
- if (this.behavior) {
158
- this.behavior[key] = module;
159
- }
160
- }
161
-
162
- // State management
163
- addError(error) {
164
- if (!this.messages.errors) {
165
- this.messages.errors = [];
166
- }
167
- this.messages.errors.push(error);
168
- this.status = 'ERROR';
169
- }
170
-
171
- addWarning(warning) {
172
- if (!this.messages.warnings) {
173
- this.messages.warnings = [];
174
- }
175
- this.messages.warnings.push(warning);
176
- }
177
-
178
- // Domain methods
179
- isActive() {
180
- return this.status === 'ENABLED' || this.status === 'ACTIVE';
181
- }
182
-
183
- needsConfiguration() {
184
- return this.status === 'NEEDS_CONFIG';
185
- }
186
-
187
- hasErrors() {
188
- return this.status === 'ERROR';
189
- }
190
-
191
- belongsToUser(userId) {
192
- return this.userId.toString() === userId.toString();
193
- }
194
-
195
- // Get the underlying behavior instance (useful for debugging or special cases)
196
- getBehavior() {
197
- return this.behavior;
198
- }
199
-
200
- // Check if a method exists (either on entity or behavior)
201
- hasMethod(methodName) {
202
- return methodName in this || (this.behavior && methodName in this.behavior);
203
- }
204
-
205
- getOptionDetails() {
206
- const options = new Options({
207
- module: Object.values(this.integrationClass.Definition.modules)[0], // This is a placeholder until we revamp the frontend
208
- ...this.integrationClass.Definition,
209
- });
210
- return options.get();
211
- }
212
-
213
- /**
214
- * Custom JSON serializer to prevent circular references (e.g. Module → Api → delegate)
215
- * and to keep API responses lightweight.
216
- * Only primitive, serialisable data needed by clients is returned.
217
- */
218
- toJSON() {
219
- return {
220
- id: this.id,
221
- userId: this.userId,
222
- entities: this.entities,
223
- config: this.config,
224
- status: this.status,
225
- version: this.version,
226
- messages: this.messages,
227
- // Expose userActions if they were loaded/attached elsewhere
228
- userActions: this.userActions,
229
- };
230
- }
231
- }
232
-
233
- module.exports = { Integration };
@@ -1,144 +0,0 @@
1
- const _ = require('lodash');
2
- const { mongoose } = require('../../database/mongoose');
3
- const { expect } = require('chai');
4
- const { IntegrationBase } = require("../integration-base");
5
- const {Credential} = require('../../module-plugin/credential');
6
- const {Entity} = require('../../module-plugin/entity');
7
- const { IntegrationMapping } = require('../integration-mapping')
8
- const {IntegrationModel} = require("../integration-model");
9
-
10
- describe(`Should fully test the IntegrationBase Class`, () => {
11
- let integrationRecord;
12
- let userId;
13
- const integration = new IntegrationBase;
14
-
15
- beforeAll(async () => {
16
- await mongoose.connect(process.env.MONGO_URI);
17
- userId = new mongoose.Types.ObjectId();
18
- const credential = await Credential.findOneAndUpdate(
19
- {
20
- user: this.userId,
21
- },
22
- { $set: { user: this.userId } },
23
- {
24
- new: true,
25
- upsert: true,
26
- setDefaultsOnInsert: true,
27
- }
28
- );
29
- const entity1 = await Entity.findOneAndUpdate(
30
- {
31
- user: this.userId,
32
- },
33
- {
34
- $set: {
35
- credential: credential.id,
36
- user: userId,
37
- },
38
- },
39
- {
40
- new: true,
41
- upsert: true,
42
- setDefaultsOnInsert: true,
43
- }
44
- );
45
- const entity2 = await Entity.findOneAndUpdate(
46
- {
47
- user: userId,
48
- },
49
- {
50
- $set: {
51
- credential: credential.id,
52
- user: userId,
53
- },
54
- },
55
- {
56
- new: true,
57
- upsert: true,
58
- setDefaultsOnInsert: true,
59
- }
60
- );
61
- integrationRecord = await IntegrationModel.create({
62
- entities: [entity1, entity2],
63
- user: userId
64
- });
65
- integration.record = integrationRecord;
66
- });
67
-
68
- afterAll(async () => {
69
- await Entity.deleteMany();
70
- await Credential.deleteMany();
71
- await IntegrationMapping.deleteMany();
72
- await IntegrationModel.deleteMany();
73
- await mongoose.disconnect();
74
- });
75
-
76
- beforeEach(() => {
77
- integration.record = integrationRecord;
78
- })
79
-
80
- describe('getIntegrationMapping()', () => {
81
- it('should return null if not found', async () => {
82
- const mappings = await integration.getMapping('badId');
83
- expect(mappings).to.be.null;
84
- });
85
-
86
- it('should return if valid ids', async () => {
87
- await integration.upsertMapping('validId', {});
88
- const mapping = await integration.getMapping('validId');
89
- expect(mapping).to.eql({})
90
- });
91
- })
92
-
93
- describe('upsertIntegrationMapping()', () => {
94
- it('should throw error if sourceId is null', async () => {
95
- try {
96
- await integration.upsertMapping( null, {});
97
- fail('should have thrown error')
98
- } catch(err) {
99
- expect(err.message).to.contain('sourceId must be set');
100
- }
101
- });
102
-
103
- it('should return for empty mapping', async () => {
104
- const mapping = await integration.upsertMapping( 'validId2', {});
105
- expect(_.pick(mapping, ['integration', 'sourceId', 'mapping'])).to.eql({
106
- integration: integrationRecord._id,
107
- sourceId: 'validId2',
108
- mapping: {}
109
- })
110
- });
111
-
112
- it('should return for filled mapping', async () => {
113
- const mapping = await integration.upsertMapping('validId3', {
114
- name: 'someName',
115
- value: 5
116
- });
117
- expect(_.pick(mapping, ['integration', 'sourceId', 'mapping'])).to.eql({
118
- integration: integrationRecord._id,
119
- sourceId: 'validId3',
120
- mapping: {
121
- name: 'someName',
122
- value: 5
123
- }
124
- })
125
- });
126
-
127
- it('should allow upserting to same id', async () => {
128
- await integration.upsertMapping('validId4', {});
129
- const mapping = await integration.upsertMapping('validId4', {
130
- name: 'trustMe',
131
- thisWorks: true,
132
- });
133
- expect(_.pick(mapping, ['integration', 'sourceId', 'mapping'])).to.eql({
134
- integration: integrationRecord._id,
135
- sourceId: 'validId4',
136
- mapping: {
137
- name: 'trustMe',
138
- thisWorks: true,
139
- }
140
- })
141
- });
142
- })
143
-
144
- });