Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[v0.36] Adds ability to test api functions #3207

Merged
merged 30 commits into from
Aug 12, 2021

Conversation

dac09
Copy link
Collaborator

@dac09 dac09 commented Aug 4, 2021

Closes #2765
Closes #849

What does this do?

This PR adds built-in capability to RW, to test your api functions (i.e. functions generated with yarn rw g function).

This is a non-breaking change 🍾 - all existing functions will still work as is.

Changes include:

  • Various build process changes to support nesting functions in a folder, along with their tests and scenarios. This follows the same convention as most other redwood generators
  • generate function now generates test and scenario files
  • Adds helper functions to @redwoodjs/testing - mockHttpEvent, mockContext and mockSignedWebhook to use in your unit tests, to make it a lot cleaner
  • Changes @redwoodjs/testing package to expose testing utilities from @redwoodjs/testing/web and @redwoodjs/testing/api so it doens't interfere with each other
  • Uses module mapper in jest configs to make the above change non-breaking for existing projects

A note on nesting functions:

For example, with a target function endpoint name of hello, you could save the function file in one of the following ways:

api/src/functions/hello.js
api/src/functions/hello/hello.js
api/src/functions/hello/index.js


Outstanding

  • Fix on Windows
  • Figure out what we're doing about importing from @redwoodjs/testing because this is currently breaking storybook

dac09 added 12 commits July 30, 2021 20:49
…om-functions-test

* 'main' of github.com:redwoodjs/redwood:
  Purged cached Magic.link token at logout (redwoodjs#2862)
  Prerender non-index pages (redwoodjs#3113)
  Adds loginHandler to dbAuth for custom login checks (redwoodjs#3111)
  Adds async/await to beforeResolver rule function invocation (redwoodjs#3072)
  Supabase to support Twitch auth (redwoodjs#3134)
  fix(project-generator): Add RW_PATH to execa options (redwoodjs#3143)
…om-functions-test

* 'main' of github.com:redwoodjs/redwood:
  Better SEO by default on every pages (redwoodjs#3026)
  Add more info in the terminal | Tweak  gitpod settings (redwoodjs#3185)
  Undo accidentally vscode settings change (redwoodjs#3184)
  Upgrade RHF to v7.11.0 (redwoodjs#3043)
  [setup tailwind] Include tailwind by adding directives to index.css (redwoodjs#3181)
  Configure cloud based development for contributors (redwoodjs#3150)
…om-functions-test

* 'main' of github.com:redwoodjs/redwood:
  Auth test: Fix typings (redwoodjs#3155)
…om-functions-test

* 'main' of github.com:redwoodjs/redwood:
  misc upgrades (redwoodjs#3194)
  Use user babel.config.js on api side. (redwoodjs#3187)
  Ignore d.ts file in glob imports (redwoodjs#3188)
  Remove gitpod advertisement.
@redwoodjs-bot redwoodjs-bot bot added this to New issues in Current-Release-Sprint Aug 4, 2021
@dac09 dac09 requested review from peterp and dthyresson August 4, 2021 20:23
Copy link
Contributor

@dthyresson dthyresson left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks great.

So, I think an example test might be something close to:

import { handler } from '../entryHtml'
import { mockHttpEvent } from '....

describe('EntryHTML function', () => {
  scenario('Returns decoded content from an Entry', async () => {
    const entryHtmlEvent = mockHttpEvent({
      queryStringParameters: {
        id: 'find-me-by-id-one',
      },
    })

    const result = await handler(entryHtmlEvent, null)
    expect(result.statusCode).toBe(200)
    expect(result.body).toEqual(expect.stringContaining('Welcome to RedwoodJS'))
  })

  scenario('Returns 404 for an entry that cannot be found', async () => {
    const entryHtmlEvent = mockHttpEvent({
      queryStringParameters: {
        id: 'entry-does-not-exist',
      },
    })

    const result = await handler(entryHtmlEvent, null)
    expect(result.statusCode).toBe(404)
  })
})

packages/testing/src/apiTestingMocks.ts Outdated Show resolved Hide resolved
packages/internal/src/files.ts Outdated Show resolved Hide resolved
Copy link
Contributor

@peterp peterp left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is looking super promising @dac09. I would love to see some of it re-architectured (I've left some comments)

I get the feeling that splitting this PR into multiple PR's would make is easier to have conversations about it because there's a lot going on here! My preference would be to focus on the proxy api server submodule functions first.

Let me know what you think, and please let me know if there's anything that's not clear.

packages/internal/src/build/api.ts Outdated Show resolved Hide resolved
@@ -27,29 +28,93 @@ export const cleanApiBuild = () => {
rimraf.sync(path.join(rwjsPaths.generated.prebuild, 'api'))
}

export const getPrebuildOutputOptions = (
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have some problems with:

a) the name of this function because it doesn't tell you what it's actually doing: it looks like it's generating files to supported "api server functions" that are nested in subdirectories.

So let's give this concept a name that describes what it's doing? What are we trying to solve? We want people to be able place "api server functions" into "subfolders"

generateProxyModulesForApiServerSubfolderFunctions

I told you I was an objective-c developer in my earlier days 🤪

b) the place where this function is invoked. It's not actually a transpilation-step, but rather a post-build+generation step that is focused on a narrow set of files, the ones return by findApiServerFunctions.

You should:

  1. Remove this code from "prebuild", and rather invoke it afterwards in the "buildApi" function.
  2. Filter out the functions that are not important; you're only concerned with subfolder function files, and a nice way of doing this that is nicely testable is adding a isApiSubfolderFunction tester. (see files.ts::isApiFunction)
  3. Make it do all the work in this single function: iterate over the files, figure out the paths, and write the content.

Copy link
Collaborator Author

@dac09 dac09 Aug 5, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These are good points, and I totally don't like how this code reads either. I'm not sure I fully understand this yet, but once I start playing around with it maybe it will!

I'll see if I can implement your suggestions - however, this did build on the existing convention in the file before I think i.e. we were getting the path and content inline within the transpliation-step.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, so I think I understand the suggestion here now. Thanks for the detailed review Peter! Some stuff to discuss:

  1. Remove this code from "prebuild", and rather invoke it afterwards in the "buildApi" function.

I can see your logic here - but let try and convince you this is the better place for it. If the purpose of prebuild is to "remove all redwood magic", i.e. leave es6 js files that will just run, I think it makes perfect sense to leave it in prebuild. The way I see it, this step allows functions to run on all our supported providers (netlify, vercel, render, self-hosted) - and they all behave consistently.

Without generating these proxy functions, what we get is code that won't quite work on all providers, because they do not support nesting consistently. I think it also is neater that the esbuild step does only one thing - transpiles from es6 to the target file type (the function name even says transpileApi).

  1. Filter out the functions that are not important; you're only concerned with subfolder function files, and a nice way of doing this that is nicely testable is adding a isApiSubfolderFunction tester. (see files.ts::isApiFunction)

Just confirming the suggestion here, in pseudo-code, should the suggested implementation look like this?

buildApi() {
	const nonNestedApiFiles = findApiFilesWithoutNestedFunctions()
	const nestedFunctions = findNestedFiles()

	const allPrebuiltFiles = [...preBuildApiFiles(nonNestedApiFiles), ...preBuildNestedFunction(nestedFunctions)]

	return transpileApi(allPrebuiltFiles)
}
  1. Make it do all the work in this single function: iterate over the files, figure out the paths, and write the content.

If we must split the lookup of api files, question here would be there's 2 ways we could group the api files:

  • Set 1:
    a) All functions
    b) All other files

or

  • Set 2:
    a) Non-nested functions + all other files
    b) Nested functions only

Personally I prefer Set 1 - but totally open to other ideas.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I hear you, but I disagree.

I think there are two concepts:

  1. Transpile the user's RedwoodJS code in standard JavaScript.
  2. Supported serverless functions in subdirectories by moving the subdirectories and generating a proxy module.

In a broad sense I agree that they're "enabling redwood magic," but I still think they're doing two separate things to achieve that goal.

Here's a thought experiment:

A year from now we decided that "no on uses serverless functions in subdirectories anymore!" And want to delete it... we would have to scratch around in the transpilation code (that we don't want to remove), and worse yet, the return type is modified, so we would have to check all the places that the transpilation code is invoked to make sure we're doing a good job.

When introducing new functionality I try to determine what it would take to delete that code if the functionality was no longer required. The idea is that deleting code should be much easier than fixing bugs and adding new functionality. If deleting it is difficult, then fixing bugs and adding functionality is going to be really difficult because it's probably too tightly coupled, and requires you to understand more than what the functionality introduces.

Alternatively if you added it in the way I suggested above then deleting it would be removing the invocation of a single function and removing that function. (Also super easy to test.)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you want we can pair and refactor this implementation.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Refactored :) - theres is a little down side, which is that I'm having to move files. But if we wanted to undo this, you literally have to remove one line.

packages/internal/src/build/api.ts Outdated Show resolved Hide resolved
packages/internal/src/build/api.ts Outdated Show resolved Hide resolved
Comment on lines 45 to 47
// If the function is nested in a folder
// put it into the special _build directory at the same level as functions
// then re-export it
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not exactly following why you're placing it in an _build directory?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

E.g.: This comment tells me what you're doing, but I don't understand the motivation.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same as before:

Here's the concept: #2765 (comment)

Idea actually came from our discussion on this few weeks ago ;)

Comment on lines 62 to 69
const reExportPath =
isIndexFile || fileName === folderName
? path.join(
rwjsPaths.generated.prebuild,
'api/src/functions',
folderName
) + '.js'
: null
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

pathForProxyApiSubfolderFunction

Comment on lines 11 to 12

export * from './apiTestingMocks'
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I actually think we should split the these into @redwoodjs/testing/api and @redwoodjs/testing/web.

I've had a helleva time trying to figure out why storybook wasn't working because I accidentally imported a NodeJS library.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah this code is organised in a difficult way.

Personally, I'd prefer not to make this change right now because then it becomes a breaking change

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done, but using module mapper so its not a breaking change.

…ood into feature/custom-functions-test

* 'feature/custom-functions-test' of github.com:dac09/redwood:
  Enable generating gitpods for forks (redwoodjs#3206)
Copy link
Contributor

@dthyresson dthyresson left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Now I learned about Exclude.

@dac09 dac09 marked this pull request as draft August 5, 2021 17:35
@dac09
Copy link
Collaborator Author

dac09 commented Aug 5, 2021

Converting to draft - till we decide on where to place the api testing helpers. Currently this is causing storybook to fail.

Also, windows tests...

Update: Resolved both.

@redwoodjs-bot redwoodjs-bot bot moved this from New issues to In progress (priority) in Current-Release-Sprint Aug 9, 2021
@thedavidprice thedavidprice self-requested a review August 9, 2021 18:07
@thedavidprice thedavidprice moved this from In progress (priority) to v0.36 in Current-Release-Sprint Aug 9, 2021
Copy link
Contributor

@peterp peterp left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some small suggestions!

packages/testing/package.json Show resolved Hide resolved
packages/testing/src/index.ts Outdated Show resolved Hide resolved
packages/internal/package.json Outdated Show resolved Hide resolved
packages/internal/src/__tests__/build_api.test.ts Outdated Show resolved Hide resolved
@thedavidprice
Copy link
Contributor

Would be RadSauce to get this into v0.36 — seems possible judging from the current review status. Just keep me posted either way.

@dthyresson
Copy link
Contributor

Would be RadSauce to get this into v0.36 — seems possible judging from the current review status. Just keep me posted either way.

I just finished up documenting actual code examples for functions and webhook testing using this PR -- and it worked great.

Draft at moment with just the code examples here: https://github.com/redwoodjs/redwoodjs.com/pull/774

Copy content around the examples to come soon.

I wanted to get the examples working first before describing.

Copy link
Contributor

@thedavidprice thedavidprice left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is looking amazing.

  • Destroy function is working great!
  • can't get the generated Function test to pass

Reproduce:

  • yarn rw g function foo
  • yarn rw test

Here's the error for the specific test:

 FAIL   api  api/src/functions/foo/foo.test.ts
  ● Test suite failed to run

    Cannot find module '~__REDWOOD__USER_ROUTES_FOR_MOCK' from '../node_modules/@redwoodjs/testing/dist/MockProviders.js'

    Require stack:
      /Users/price/Repos/tdp-redwood-tutorial-test/node_modules/@redwoodjs/testing/dist/MockProviders.js
      /Users/price/Repos/tdp-redwood-tutorial-test/node_modules/@redwoodjs/testing/dist/customRender.js
      /Users/price/Repos/tdp-redwood-tutorial-test/node_modules/@redwoodjs/testing/dist/index.js
      src/functions/foo/foo.test.ts

      at Resolver.resolveModule (../node_modules/jest-resolve/build/resolver.js:311:11)
      at Object.<anonymous> (../node_modules/@redwoodjs/testing/dist/MockProviders.js:32:5)

NOTE: I was using the yarn dev --cwd [path to test project] test for testing. It's possible it didn't set the correct paths for dist/

Do I need to rwfw project:sync instead?

@thedavidprice
Copy link
Contributor

@dac09 and @dthyresson As a next step once this is merged, let's add this to the existing E2E. Would be great to add along with this PR, but I don't want to hold it up.

…om-functions-test

* 'main' of github.com:redwoodjs/redwood:
  webpack and misc upgrades (redwoodjs#3235)
  upgrade Prisma v2.29.0 (redwoodjs#3233)
  Fix API sourcemaps (babel+esbuild) for debugging (redwoodjs#3230)
  set canary as default (redwoodjs#3228)
  Fix issue where Clerk client is sometimes unset (redwoodjs#3224)
  Fix: Custom auth provider Private route redirect (redwoodjs#2872)
  fix(babel): API side include babel runtime as deps | Bump babel (redwoodjs#3220)
…ood into feature/custom-functions-test

* 'feature/custom-functions-test' of github.com:dac09/redwood:
  Update packages/internal/src/__tests__/build_api.test.ts
@dac09
Copy link
Collaborator Author

dac09 commented Aug 12, 2021

NOTE: I was using the yarn dev --cwd [path to test project] test for testing. It's possible it didn't set the correct paths for dist/

Do I need to rwfw project:sync instead?

@thedavidprice yeah I don't know how that command works really :). Here's an example with Rufus https://s.tape.sh/m3szgH9b

@dac09
Copy link
Collaborator Author

dac09 commented Aug 12, 2021

All comments addressed!

@dac09 dac09 changed the title Adds ability to test api functions [v0.36] Adds ability to test api functions Aug 12, 2021
Copy link
Contributor

@peterp peterp left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🦦

Current-Release-Sprint automation moved this from v0.36 to Ready to merge Aug 12, 2021
@thedavidprice
Copy link
Contributor

thedavidprice commented Aug 12, 2021

Ship it!!

image

@dac09 dac09 merged commit 5c19235 into redwoodjs:main Aug 12, 2021
Current-Release-Sprint automation moved this from Ready to merge to Done Aug 12, 2021
@dac09 dac09 deleted the feature/custom-functions-test branch August 12, 2021 16:18
dac09 added a commit that referenced this pull request Aug 12, 2021
…ed-babel-location-require-hook

* 'main' of github.com:redwoodjs/redwood:
  [v0.36] Adds ability to test api functions (#3207)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
No open projects
4 participants