-
Notifications
You must be signed in to change notification settings - Fork 1
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
Add R API container to dev dependencies script, and consume R API from app #10
Conversation
a7e9485
to
8f1a0dd
Compare
Codecov ReportAttention: Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #10 +/- ##
==========================================
+ Coverage 50.62% 53.50% +2.87%
==========================================
Files 16 22 +6
Lines 401 514 +113
Branches 25 39 +14
==========================================
+ Hits 203 275 +72
- Misses 192 231 +39
- Partials 6 8 +2 ☔ View full report in Codecov by Sentry. |
06e142e
to
1be396f
Compare
.env.example
Outdated
@@ -1,2 +1,3 @@ | |||
CI=0 | |||
DATABASE_URL="postgresql://daedalus-web-app-user:changeme@localhost:5432/daedalus-web-app" | |||
NUXT_PUBLIC_R_API_BASE=http://localhost:8001/ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This doesn't mean the API is public, it's just a way of automatically setting a config variable in nuxt.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So it needs to have the NUXT_PUBLIC_
prefix to become available in Nuxt?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It ought to work without 'PUBLIC' but I couldn't immediately make it do so. Documentation: https://nuxt.com/docs/guide/going-further/runtime-config#environment-variables
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
OK I managed to remove the 'PUBLIC' part :)
// @vitest-environment nuxt | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These comments used to tell vitest to use a nuxt environment for these tests, but after I got stuck for a while trying to debug a test and finally worked out that it wasn't using a nuxt environment, I decided to set that as the default in vitest.config.ts.
7c5be26
to
cd0b746
Compare
cd0b746
to
dd6c7bf
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Works for me!
There seem to be a lot of DB script changes showing in the diff? Do these belong in the parent branch? Do you need to push the latest of that?
Missing unit tests for the server endpoints? Codecov is grumbling as well as the failing Playwright workflow.
Feels to me like the last missing piece is how a store is going to fit into all this - one for the next sprint perhaps.
.env.example
Outdated
@@ -1,2 +1,3 @@ | |||
CI=0 | |||
DATABASE_URL="postgresql://daedalus-web-app-user:changeme@localhost:5432/daedalus-web-app" | |||
NUXT_PUBLIC_R_API_BASE=http://localhost:8001/ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So it needs to have the NUXT_PUBLIC_
prefix to become available in Nuxt?
README.md
Outdated
@@ -29,31 +29,36 @@ The tests under e2e, which are run by playwright, are for testing the full-stack | |||
|
|||
## Local development | |||
|
|||
### Setup | |||
### If it's the first time setting up |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could this have a corresponding heading further down at the start of the bits that you need to do every time?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's what I intended with 'Other ways of serving the app and dependencies' heading, I can rename that to be more clearly a contrasting heading
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah, right I thought that was 'alternatives' - I really meant running dependencies and running the app, which you need to do every time you, say, restart your machine, as opposed to installing Docker which you only have to do once.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Updated README
@@ -18,5 +18,6 @@ const prisma = globalThis.prismaGlobal ?? prismaClientSingleton(); | |||
|
|||
export default prisma; | |||
|
|||
if (process.env.NODE_ENV !== "production") | |||
if (process.env.NODE_ENV !== "production") { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think you've switched off linting errors for one line ifs with braces - which is good - I think elsewhere we actually enforce these using somewhat different eslint config. Would be good to be as consistent as possible there.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is now enforced, because lessOpinionated reverts us to the eslint 'curly' rule being set to 'error' in 'all' cases: https://eslint.org/docs/latest/rules/curly
server/api/versions/index.get.ts
Outdated
@@ -0,0 +1,28 @@ | |||
import { fetchRApi } from "@/server/utils/rApi"; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could you simplify this location of this file somewhat? I get the impression from the docs, that you could just put this code in server/api/versions.ts
- is it because you will have some POSTs which will need to go into a .post.ts
file? But even then, I don't think you need to make a separate folder for each path.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
OK, let's simplify the dir structure for now
server/api/versions/index.get.ts
Outdated
daedalusApi: response!.data!["daedalus.api"], | ||
daedalusWebApp: packageJson.version, | ||
}; | ||
}, { maxAge: 60 /* Cache response server-side for 1 minute */ }); // https://nitro.unjs.io/guide/cache |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This feels like it ought to be defined as a global setting somewhere, for responses which are appropriate for caching.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
60 in particular? I think the appropriate cache duration might vary by endpoint. It feels premature to make that a shared setting, until we have at least two cached endpoints.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I ended up getting rid of the cacheing because it was causing confusion while debugging.
server/utils/rApi.ts
Outdated
throw createError({ | ||
statusCode: 500, | ||
message: "Internal Server Error", | ||
data: { | ||
rApiStatusCode: error.response?.status, | ||
rApiResponseBody: error.data, | ||
rApiResponseErrors: errorInformationString(error), | ||
}, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it would be better (and is generally our standard practice) for error responses from the web app api to mirror those from the R API, i.e. same format, include an errors
array. I think we generally tend to just pass error responses through, preserving status code and errors, and including null data - unless there's some particular reason why we don't want to surface R API errors to the front end - we may choose not to display them to the user of course.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I gave this a good try. Below I'll give a comparison of the responses from the R API and the web app API, in the scenario when an error occurs:
Web app API (much of the format of this is determined by Nuxt):
> curl http://localhost:3000/api/versions
{"url":"/api/versions","statusCode":404,"statusMessage":"Not Found","message":"NOT_FOUND: Resource not found","stack":"","data":[{"error":"NOT_FOUND","detail":"Resource not found"}]}
R API (I am presuming much of the format of this is determined by porcelain):
curl http://localhost:8001/
{"status":"failure","errors":[{"error":"NOT_FOUND","detail":"Resource not found"}],"data":null}
components/SideBar.vue
Outdated
@@ -52,37 +58,45 @@ | |||
<script lang="ts" setup> | |||
import { CIcon } from "@coreui/icons-vue"; | |||
|
|||
const { data: versionData } = useFetch("/api/versions"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we cast this to the relevant response type?
server/api/versions/index.get.ts
Outdated
interface SuccessResponse { | ||
daedalusModel: string | ||
daedalusApi: string | ||
daedalusWebApp: string | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It feels like this sort of api model type is going to be useful to the client, so it might be better to export it with a less generic name, and to define it in a models
or types
folder perhaps.
48de4d3
to
1897a64
Compare
Friday pm playwright
In fact this PR introduces a new DB script 'wait-for-db', which is required by the scripts 'run dependencies' and 'run dev dependencies' which are new features from this branch. They also imply a few other small changes to db scripts (like making sure they are able to return a branch name while on CI). |
93e284b
to
72869ff
Compare
|
||
const visible = defineModel("visible", { type: Boolean, required: true }); | ||
const largeScreen = ref(true); | ||
|
||
const breakpoint = 992; // CoreUI's "lg" breakpoint |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
From here on in this file the changes are only formatting.
@@ -0,0 +1,163 @@ | |||
{ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This file is automatically generated (but can also be manually edited). If a crumb of context is needed, see the new file tests/integration/INTEGRATION_TESTING.md
|
||
set -eux | ||
|
||
echo "NUXT_R_API_BASE=$NUXT_R_API_BASE" > .env |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This file is a bit of infrastructure to make it convenient to add lots more env vars at once to GitHub Actions much easily (assuming we'll get more of them in the future)
72869ff
to
9cdbff2
Compare
Also, use a mixture of integration (with Mockoon) testing and unit testing to test the web server API and its interaction with the front end and the R API.
9cdbff2
to
1a82a3b
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Various meandering remarks, some of which change course partway though!
Pretty much all suggestions could be ignored or done in another branch - up to you!
Web app API (much of the format of this is determined by Nuxt):
curl http://localhost:3000/api/versions
{"url":"/api/versions","statusCode":404,"statusMessage":"Not Found","message":"NOT_FOUND: Resource not found","stack":"","data":[{"error":"NOT_FOUND","detail":"Resource not found"}]}
Ah sorry, I didn't realise that Nuxt was so bossy about api response format. This seems like a good compromise, allowing us to separate out error code and detail, and with potential for multiple errors. Let's see how it works for us.
I wouldn't bother with Mockoon myself, it seems like a brittle overhead that's going to be a pain to maintain. I get the point about e2e vs integration, and the idea of not going outside the bounds of the repo for the former... but to be honest we've always found it most instructive to hit the the real R API in our web API integration tests as well as e2e, since that gives us early warning of any incompatibilities, and it's easy for us to address those since we own the R APIs too.
UPDATE: Hmm, ok I may have been persuaded out of that position by seeing that a lot of the integration tests you've done are around error handling, and errors are quite difficult to engineer in the real API, and the error handling does involve multiple modules, so it really does need integration testing. So Mockoon does have a use, but I'd suggest using it fairly sparingly, for special cases like error handling, and use the e2e tests for most use cases.
tests/unit/server/api/README.md
Outdated
|
||
However, for testing our own endpoints, there is no way to directly invoke the functions exported from our API endpoints, other than by sending requests to the endpoints they define. As a result, we are required to run our server from the test, which we achieve by using the @nuxt/test-utils/e2e module (and we use the module's in-built [`fetch`](https://nuxt.com/docs/getting-started/testing#fetchurl-1) function to send the requests). The fact that Vitest is running a whole test server means these tests are not zoomed-in unit tests that are able to test a small piece of code, but are more like integration tests, since they run the server entirely, and they also (as we'll come onto later) use a mock server to mock responses from external APIs (namely, the R API). | ||
|
||
Although the set-up just described is not very unit-like and is more like an integration test, it is the closest to unit-testing that the Nuxt eco-system seems to currently offer if you need to test calling any API endpoints which belong to your server. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't really think we need to be this confessional about it. It's ok to just have integration tests for the edges of the system I think!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is an outdated README that doesn't exist in the PR files any more (though it did yesterday morning when you wrote this!). The up-to-date documentation is in the file called INTEGRATION_TESTING.md which is significantly different and post-dates your discovery of a nice way to do unit testing: https://github.com/jameel-institute/daedalus-web-app/pull/10/files#diff-946b7aec3a9dfaeccc6649ec573a1b47c732e2f13fa236fec90af741e7cf4a90
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sorry, I think i made this remark before I realised you hadn't resubmitted yet, and then I forgot to delete it! 😆
with: | ||
data-file: ./mocks/mockoon.json | ||
port: 8001 | ||
- name: Check API server is running |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
- name: Check API server is running | |
- name: Check mocked R API server is running |
I think this is correct - it's not the nuxt API you're checking here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yep thanks
- name: Set environment variables for app | ||
run: scripts/ci/copy-env-vars-to-dot-env-file | ||
env: | ||
NUXT_R_API_BASE: ${{ vars.NUXT_R_API_BASE }} | ||
# Note - non-secret variables are stored under the 'vars' context, while secrets will be stored under the 'secrets' context |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If this is going to be used in multiple workflows, you might want to pull this out into a shared action, like these:
https://github.com/mrc-ide/packit/tree/05560383aef00035a62eae605094e016eb6bc03c/.github/actions/build-api
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'll bear it in mind for if it gets used in more than two workflows!
README.md
Outdated
To run unit tests, component tests and integration tests, first start the Mockoon server: | ||
```bash | ||
npx mockoon-cli start --data ./mocks/mockoon.json | ||
``` |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I shouldn't need mockoon if I just want to run the unit/component tests though?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
True, true, true-chez. Updated.
@@ -51,38 +57,47 @@ | |||
|
|||
<script lang="ts" setup> | |||
import { CIcon } from "@coreui/icons-vue"; | |||
import type { VersionData } from "@/types/apiResponseTypes"; | |||
|
|||
const { data: versionData } = useFetch("/api/versions") as { data: Ref<VersionData> }; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It seems likely that this pattern will be repeated a lot, so we'll probably eventually want a generic typs something like
export interface APIResponse<T> {
data: Ref<T>
}
so then this line would become
const { data: versionData } = useFetch("/api/versions") as APIResponse<VersionData>;
..but then I suspect maybe we'll also eventually have some sort of useAPI
which wraps the raw useFetch
and just gives back the data you're interested in, and helps with error handling. So probably not one for this PR.
UPDATE: I see below that you've got ServerApiResponse
, but I think that refers to responses from the R API?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ServerApiResponse
was named to try and distinguish it from the R API, so that'll have to be re-named... if we simply call it ApiResponse
might that actually be clearer?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Though ServerApiResponse
's purpose differs from what you're talking about - ServerApiResponse
(well, types extended from it) are to be used inside the API routes to dictate the format of the response's data, rather than to be used wrapping a useFetch.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, exactly. I think probably the response types from the R API (used in the web app backend) and the types from the web app backend (used in the front end) should probably live in different modules, and maybe a "shared" module for ones that happen to be the same, with whatever generic classes are useful in the two contexts. Maybe ServerApiResponse
and RApiResponse
(I think DaedalusApiResponse
would be more correct, but very verbose, and you already have rApi module...)
// Multiple web servers (or background processes) can be launched: https://playwright.dev/docs/api/class-testconfig#test-config-web-server | ||
webServer: { | ||
command: 'NUXT_HOST="127.0.0.1" NUXT_PORT="3000" npm run dev', | ||
command: "npm run build && cd .output && node ./server/index.mjs", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why this change?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This changes the e2e tests from running in dev mode to running in production mode. That's a more realistic test. It also, I think, lets us by-pass an intermittent problem I had when the dev server was responding to requests before the site was built (with that greenish Nuxt interstitial loading page), which tricked Playwright into thinking it was ready to start running the tests.
server/api/versions.get.ts
Outdated
if (versionDataResponse.errors || !versionDataResponse.data) { | ||
throw createError({ | ||
statusCode: versionDataResponse.statusCode, | ||
statusMessage: versionDataResponse.statusText, | ||
message: errorMessage(versionDataResponse.errors), | ||
data: versionDataResponse.errors, | ||
}); | ||
} else { | ||
return versionDataResponse.data; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This shared bit of logic feels like it ought to be middleware, or whatever the Nuxt equivalent is! Or just handled in rApi?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've extracted it in this commit:
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nuxt documentation for server utils is limited but it's here: https://nuxt.com/docs/guide/directory-structure/server#server-utilities
@@ -0,0 +1,22 @@ | |||
import { expect, request, test } from "@playwright/test"; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm assuming this test module is temporary since basically all e2e tests will eventually be interacting with the R APi!
Co-authored-by: Emma Russell <[email protected]>
Co-authored-by: Emma Russell <[email protected]>
…nstitute/daedalus-web-app into jidea-38-containerisation-r-api
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
One tiny comment, but it all seems to work great so I'm going to approve and merge this so I can get on with the docker stuff against the latest main with API support.
const versionDataResponse = await getVersionData(event); | ||
export default defineEventHandlerWithErrors( | ||
// TODO: Consider cacheing this server-side https://nitro.unjs.io/guide/cache | ||
defineEventHandler(async (event): Promise<VersionDataResponse> => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm a bit surprised that you're still invoking defineEventHandler
here, since your new custom defineEventHandlerWitErrors
already wraps defineEventHandler
- do you have one more layer of defineEventHandler
than you need?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I found a way around this, here is the commit: b029800
It's not yet a commit that lives in a PR.
This PR sets up the basic infrastructure required for the web app to consume data from the R API: