From c17ec33b27f504d0d135802a81d5d86885ef56ec Mon Sep 17 00:00:00 2001
From: Joe Clark <jclark@openfn.org>
Date: Sun, 10 Mar 2024 22:52:36 +0300
Subject: [PATCH] update notes

---
 packages/msgraph/src/operations.js |  9 ---
 tutorial.md                        | 95 ++++++++++++++++++++++++------
 2 files changed, 76 insertions(+), 28 deletions(-)

diff --git a/packages/msgraph/src/operations.js b/packages/msgraph/src/operations.js
index 9685fcf5a..daf4b546c 100644
--- a/packages/msgraph/src/operations.js
+++ b/packages/msgraph/src/operations.js
@@ -18,15 +18,6 @@ const getRequestHandler = () => {
 export const enableMock = (state, routes) => {
   return enable(state, routes);
 }
-
-// Every adator uses this API to override mock mode
-// The pattern is adaptor-specific
-// data is whatever you want the client to return
-// TODO: alias to addMockRule ?
-export const mock = (pattern, data, options = {}) => {
-  return mockRoute(pattern, data, options)
-}
-
 /**
  * Execute a sequence of operations.
  * Wraps `language-common/execute` to make working with this API easier.
diff --git a/tutorial.md b/tutorial.md
index 5150b4ce1..b96b40ea4 100644
--- a/tutorial.md
+++ b/tutorial.md
@@ -9,7 +9,7 @@ Here's what I've introduced
 * Operation factories
 * Operation/implementation split
 * Real runtime/job tests
-* A pattern for mocking (probably better than demanding docker containers?)
+* A pattern for mocking which SHOULD enable a live playground (!!) and maybe easier adaptor creation
 
 ### Motivations
 
@@ -20,14 +20,13 @@ Here are the problems I'm trying to solve
 * Clarity over what an operation is, and why it matters
 * Encouraging good documentation _in the right place_
   * I see lots of problems of devs documenting the wrong functions, wasting time and energy
+* (new) If we had a live playground on docs.openfn.org, how would we handle adaptors?
 
 ### Examples
 
 I've started implementing this stuff in `msgraph` (and common) to see how it all comes together
 
-I should really push this further and will try and spend some time on it before I fly.
-
-Maybe `salesforce`, `dhis2` or `http` would be better examples?
+I urgently need to start on `salesforce` to explore client-based mocking
 
 ### Issues
 
@@ -37,15 +36,12 @@ Here's what's not right yet:
 * Expanding references. I'd really like to standardise and simplify this further
   - jsdoc path vs dataValue() vs open function
   - I am sure that you can just make expand references read '$a.b.c' as a jsonpath
-* I'm not totally sold on some parts of the mocking pattern
-  - Getting mock data feels a bit hard (although there may be an answer to that here somewhere)
-  - The setclient function on the adaptor is a bit awkward
-  - Can we exclude the setClient function from the final build?
-* When the client is abstracted out (like in msgraph), it can beh ar to know what it is. You look at `impl.js` and you see request, you don't know what it is. So it's actually kinda hard to use. hmm. 
+* When the client is abstracted out (like in msgraph), it can be hard to know what it is. You look at `impl.js` and you see request, you don't know what it is. So it's actually kinda hard to use. hmm. 
+* Maybe the impl pattern makes less sense with the new mocking pattern? Depends how salesforce looks
 
 ### Operation Factories
 
-Here's an operation factory
+Here's an operation factorybeh ar
 ```js
 export const get = operation((state, url, auth, callback) => {
    // code goes here
@@ -73,7 +69,7 @@ export const create = operation((state, resource, data, callback) => {
 
 Note the extra `request` argument!
 
-I can now unit test the implementation:
+I can now unit test the implementation really cleanly:
 
 ```js
 describe('getDrive', () => {
@@ -97,13 +93,77 @@ describe('getDrive', () => {
 
 The ability to mock the implementation like this also enables real runtime testing
 
+### Mocking as first-class adaptor code
+
+What if each adaptor was able to run in a mock mode. In this mode it will mock out the client entirely and return mock data. A default data suite is included, but can be overridden.
+
+This works pretty great with undici, I think it should work nicely with client based adaptors too.
+
+First, the adaptor to expose an `enableMock` function, which is called with state (for config) and optionally some mock data. If mockdata is not provided, defaults will be used.
+
+Later, the runtime could call `enableMock` in certain circumstances (like a live playground).
+
+```js
+// mock.js (exported by index.js)
+export const enableMock = (state, routes = {}) => {
+  // setup undici to mock at the http level
+  mockAgent = new MockAgent();
+  mockAgent.disableNetConnect();
+
+  mockPool = mockAgent.get('https://graph.microsoft.com');
+
+  const mockRoutes = {
+    ...defaultRoutes,
+    ...routes
+  }
+
+  setupMockRoutes()
+}
+
+// name: { pattern, data, options }
+const defaultRoutes = {
+  'drives': { pattern: /\/drives\//, data: fixtures.driveResponse }
+}
+```
+
+Mocks work based on routing. Obvious with HTTP but I think we can do it with clients too (patterns confirm to` client.<fn>`)
+
+Each adaptor is responsible for implementing  its own mock in whatever way makes sense. We will provide strong patterns and utilitie.
+
+Now, if you want to write unit tests against this mock, you can do so
+```js
+it('with default mock', async () => {
+  const state = { ... };
+  Adaptor.enableMock(state);
+
+  const result = await Adaptor.getDrive({ id: 'abc' })(state)
+  expect(result.drives).to.eql({ default: fixtures.driveResponse })
+})
+
+it('with custom mock', async () => {
+  const state = { ... };
+
+  Adaptor.enableMock(defaultState, {
+    'drives': {
+      pattern: patterns.drives,
+      data: { x: 22 },
+      options: { once: true }
+    }
+  });
+
+  const result = await Adaptor.getDrive({ id: 'abc' })(state)
+  expect(result.drives).to.eql({ default: fixtures.driveResponse })
+})
+```
+
+Important detail: the mock should be considered frozen when activated. If you want to change mock data, call `enable` again
+
 ### Real runtime tests
 
 I really hate the existing test syntax we use right now. What I actually want to do is write a job and pass it to the actual run time so execution.
 
 So I've implemented this!
 
-
 First, we create an example job in a source file
 
 ```js
@@ -124,12 +184,15 @@ getDrive((
 In a unit test, we can:
 * Load this example source
 * Load the adaptor module
-* Set a mock client/request object in the adaptor
+* Use mock data from above (works great)
 * Pass the source, input state and adaptor into the actual runtime and compiler
 
 That gives us a test that looks like this:
 ```js
 describe('examples', () => {
+  // setup our mock with whatever data we want
+  Adaptor.enableMock({ configuration });
+
   it('get-drive', async () => {
     // Load our example code
     const source = loadExample('get-drive')
@@ -139,12 +202,6 @@ describe('examples', () => {
       id: 'xxx',
     };
 
-    // Set up the mock
-    Adaptor.setRequestHandler(async (url) => {
-      // Return some mock data (perhaps as a pre-saved fixture, or we define it in-line)
-      return { ... }
-    })
-
     // Compile and run the job against this adaptor
     const finalState = await execute(source, Adaptor, state)