Testing AWS Imports - Elaborated

https://github.com/observablehq/framework/discussions/2035

Tom Larkworthy's AWS Helpers notebook


In the notebook context, we can use an import statement to pull over all the functions we need, and they work to pass our credentials into AWS.

For example, in https://observablehq.com/@categorise/surveyslate-designer-tools

import {listObjects, getObject, putObject, listUsers, createUser, deleteUser, getUser, listAccessKeys, createAccessKey, deleteAccessKey, viewof manualCredentials, viewof mfaCode, saveCreds, listUserTags, tagUser, untagUser, iam, s3, listGroups, listGroupsForUser, addUserToGroup, removeUserFromGroup} with {REGION as REGION} from '@tomlarkworthy/aws'

Assuming that we've done the work correctly of re-factoring Tom's notebook both as an Observable Framework page and as vanilla JS, the corresponding import statement would be:

import {listObjects, getObject, putObject, listUsers, createUser, deleteUser, getUser, listAccessKeys, createAccessKey, deleteAccessKey, manualCredentials, mfaCode, saveCreds, listUserTags, tagUser, untagUser, iam, s3, listGroups, listGroupsForUser, addUserToGroup, removeUserFromGroup} from '/components/aws.js';

Since we can't alias imports in Framework the same as we do in notebooks, we explicitly define the with statement:

with {REGION as REGION} 

-as this-

const REGION = 'us-east-2'

--

Yet when attempting to import all the AWS functions we at one throws hit our first error, and the import statement doesn't resolve.

Right off the bat, we find an Observable-specific statement that works in the Framework notebook but that fails when trying to use it as standard JavaScript: invalidation.

  // viewof manualCredentials.addEventListener('input', check);
    manualCredentialsElement.addEventListener("input", check);
  invalidation.then(() => {
  // viewof manualCredentials.removeEventListener('input', check);
    manualCredentialsElement.removeEventListener("input", check);
  });

invalidation won't work in normal JS, as the runtime injects into cells (external ES modules have so such concept). As it doesn't look like I can get past this particular failure, I quickly tried to manually reconstruct manualCredentials locally to prevent getting hung up when importing it. In doing this, I wanted to see if we can pass the credentials ion our other aws.js functions:


Demo Credentials for Testing:


{
  "accessKeyId": "",
  "secretAccessKey": ""
}

display(saveCredsElement);
display(saveCreds);

... Nope. The iam constructor does not see the credentials being passed to it just yet (I can add a screenshot later.)

      ~~~js
      const iam = login || new AWS.IAM();
      display(iam)
      ~~~
      ~~~js echo
      const me = getUser()
      display(await me)
      ~~~

We can take this a few steps further -- to see whether if we locally re-create the iam, s3, and cloudFlare configuration files these can play well with the other imported functions.

const AWS = await import("https://unpkg.com/aws-sdk@2.983.0/dist/aws-sdk.min.js").then(() => window.AWS);
display(AWS)
import {config} from '/components/survey-slate-configuration.js';
import { expect } from '/components/testing.js';

const credentials = Generators.observe((next) => {
  const check = () => {
    //const creds = viewof manualCredentials.value;
    //const creds = manualCredentialsElement.value;
    const creds = manualCredentials;
    try {
      expect(creds).toBeDefined();
      const parsed = JSON.parse(creds);
      expect(parsed).toHaveProperty("accessKeyId");
      expect(parsed).toHaveProperty("secretAccessKey");
      next(parsed);
    } catch (err) {
      //next(err);
    }
  };

  // viewof manualCredentials.addEventListener('input', check);
    manualCredentialsElement.addEventListener("input", check);
  invalidation.then(() => {
  // viewof manualCredentials.removeEventListener('input', check);
    manualCredentialsElement.removeEventListener("input", check);
  });

  check();
});
display(credentials)
const login = (async() => {
  AWS.config.credentials = await credentials;
})();
const iam = login || new AWS.IAM();
display(iam)
const s3 = login || new AWS.S3({ region: REGION });
display(s3)

Looks promising! We can see that the credentials are set correctly.


const me = getUser();
display(await me)
const myTags = await listUserTags(me.UserName);
display(myTags)
const surveys = myTags['designer'].split(" ");
display(surveys)

Looks like this is about as far as I need to go to redefine all the AWS functions (below). Good enough for now.


import {listObjects, getObject, putObject, listUsers, createUser, deleteUser, getUser, listAccessKeys, createAccessKey, deleteAccessKey, mfaCode, listUserTags, tagUser, untagUser, listGroups, listGroupsForUser, addUserToGroup, removeUserFromGroup} from '/components/aws.js';
display(listObjects)
display(getObject)
display(putObject)
display(listUsers)
display(createUser)
display(deleteUser)
display(getUser)
display(listAccessKeys)
display(createAccessKey)
display(deleteAccessKey)
/// Commenting this out here b/c we don't want to keep invoking it as it jumps across the DOM. We can bind it.
///display(manualCredentialsElement)
display(manualCredentials)
display(mfaCode)
display(saveCreds)
display(saveCreds)
display(listUserTags)
display(tagUser)
display(untagUser)
display(iam)
display(s3)
display(listGroups)
display(listGroupsForUser)
display(addUserToGroup)
display(removeUserFromGroup)