Skip to content
This repository was archived by the owner on Dec 20, 2023. It is now read-only.

Context tests and contextualize #70

Merged
merged 13 commits into from
Nov 8, 2023
Merged

Context tests and contextualize #70

merged 13 commits into from
Nov 8, 2023

Conversation

tiptenbrink
Copy link
Collaborator

@tiptenbrink tiptenbrink commented Nov 8, 2023

This PR adds everything necessary to write tests for the functions in apiserver/data/context, i.e. the ones that can be overridden easily for testing using Context (the current version introduced in #64). It also adds an initial routing test and some docs on when/how to use modules vs context functions.

  • Modules are for complex interactions that use many different context functions, particularly in the case where context functions could be reused in multiple ways. They should not know about HTTP or how the context functions get their data (i.e. shouldn't call the data/api functions) and should not instantiate any connections themselves. This is still a somewhat leaky abstraction, as we still have source_session. Modules should be tested, as they only call context functions which can be easily replaced in tests.
  • Context functions should call the data API functions (data/api) and other functions if they need business logic. The amount of logic should be minimized, they should really be all about loading and storing data. If possible, logic should be separated into different functions that can be easily unit tested. Context functions shouldn't really need to be tested, but the functions they call should be.

Sometimes, a router or module wants to directly call just a single data/api function. To make it testable, they should only call context functions. However, this adds an additional layer between the data API functions and using them and it is quite a lot of boilerplate to copy them, instantiate connections, add typing, add context stubs, etc. So, a new solution was invented:

  • ctxlize is a new function in the datacontext package. It takes any function and a wrapper function and then allows it to be called with a WrapContext. The arguments can be transformed in an arbitrary way by the wrapper function, which is defined at the application level. We use it to turn the AsyncConnection argument into a Source argument by instantiating a connection in the wrapper function. Example:
# Original function
async def get_event_user_points(
    conn: AsyncConnection, event_id: str
)
# Calling it using ctxlize, dsrc is of Source type
event_users = await ctxlize(get_event_user_points, conn_wrap)(
        cd.wrap, dsrc, event_id
)
# conn_wrap definition (types have been collapsed)
# `c` is original function
def conn_wrap(c):
    async def wrapped(r: Source, *args, **kwargs):
        async with get_conn(r) as conn:
            return await c(conn, *args, **kwargs)

    return wrapped

It doesn't require stubs to be written nor a decorator on the original function. You simply create a WrapContext for each test and add only the functions you want to change to it and then replace the wrap attribute of the app's Code object. This means you do have to check yourself that they match properly, this isn't checked like it is for the usual way of creating context functions. The types look quite complicated but if you use it with Pylance in VS Code it should work quite well.

Some functions still need to be switched to this. Also, the require_admin etc. needs proper context functions and probably some refactoring. But for now this should be enough.

@tiptenbrink tiptenbrink changed the title More tests Context tests and contextualize Nov 8, 2023
@tiptenbrink tiptenbrink merged commit c493c03 into main Nov 8, 2023
@tiptenbrink tiptenbrink deleted the more-tests branch November 8, 2023 13:46
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

1 participant