keystone icon indicating copy to clipboard operation
keystone copied to clipboard

Application tries to reuse test state during normal runtime (breaks when restarting after running tests)

Open NoTuxNoBux opened this issue 2 years ago • 0 comments

Please add steps to reproduce the problem

  1. Use Docker or Podman with a compose.yaml that has DATABASE_URL set to a PostgreSQL connection string, e.g. postgres://foo:bar@server/app (see below for an example).
  2. Start application using podman-compose up -d or similar. Folder .keystone/admin is generated, and application starts successfully.
  3. Write a database test using SQLite as provider, and a local file path, e.g. in /tmp (see below for an abbreviated example).
  4. Run tests, using e.g. jest --testTimeout=60000. The folder .keystone/tests is generated, and tests pass.
  5. Restart the application using podman-compose restart app, Startup fails with the error message below.
PrismaClientInitializationError: error: Error validating datasource `sqlite`: the URL must start with the protocol `file:`.
  -->  schema.prisma:5
   | 
 4 | datasource sqlite {
 5 |   url      = env("DATABASE_URL")
   | 

Validation Error Count: 1
    at Object.loadEngine (/app/node_modules/.prisma/client/runtime/index.js:35591:19)
    at Object.instantiateLibrary (/app/node_modules/.prisma/client/runtime/index.js:35520:5)
    at Object.start (/app/node_modules/.prisma/client/runtime/index.js:35670:5)
    at Object.connect (/app/node_modules/@keystone-6/core/dist/initConfig-64706830.cjs.dev.js:3138:13)
    at setupInitialKeystone (/app/node_modules/@keystone-6/core/scripts/dist/keystone-6-core-scripts.cjs.dev.js:422:3)
    at initKeystone (/app/node_modules/@keystone-6/core/scripts/dist/keystone-6-core-scripts.cjs.dev.js:166:35) {
  clientVersion: '3.11.0',
  errorCode: 'P1012'
}

Please describe what you expected to happen

The application starts up without any issues, and tests also run without any issues.

Please add contextual information such as your node version (node -v), or the web browser you used

The problem seems to be that in step 5 above, the .keystone/admin folder is removed and regenerated but, before it is regenerated, Keystone seems to select .keystone/tests for use during actual runtime of the application. When that happens, it starts using the schema.prisma from .keystone/tests, which has the SQLite provider set, and then tries to apply the DATABASE_URL environment variable containing PostgreSQL from compose.yaml to it. If you remove .keystone/tests first, the application starts normally again.

Removing schema.prisma alone is not enough, Keystone still seems to want to descend into .keystone/tests for some reason.

Test and normal application state appears to not be (properly) separated, due to which they are interfering with each other's data. I also looked if it was necessary to set some sort of environment variable when running tests, but could not find any.

Versions

  • Keystone version: 25 March 2022
  • Node.JS version: 16 (Alpine)

compose.yaml:

services:
    app:
        image: node:16-alpine
        working_dir: /app
        command: npm run dev
        ports:
            - "3000:3000"
        volumes:
            - ./:/app
        environment:
            DATABASE_PROVIDER: postgresql
            DATABASE_URL: postgres://postgres:bar@postgresql/app

    postgresql:
        image: postgres:13-alpine
        ports:
            - "5432:5432"
        volumes:
            - ./.containers/postgresql/:/var/lib/postgresql/data
        environment:
            POSTGRES_USER: postgres
            POSTGRES_PASSWORD: bar

Abbreviated Test Example

(Might contain errors, I copied over the code to get you started more quickly.)

import { TestEnv } from "@keystone-6/core/testing";
import { KeystoneContext } from "@keystone-6/core/types";
import { getTenantFilterQuery, UserGroup } from "../../../src/auth";
import {
    createTenant,
    createTestContextForUser,
    createUser,
    UserId,
} from "../../fixtures";

describe("Access control", () => {
    let testEnv: TestEnv;
    let context: KeystoneContext;

    beforeEach(async () => {
        ({ context, testEnv } = await provisionTestDatabase());
    });

    afterEach(async () => {
        await testEnv.disconnect();
    });

    test(`Foo`, async () => {
        // ...
    });
});

async function provisionTestDatabase(): Promise<{
    context: KeystoneContext;
    testEnv: TestEnv;
}> {
    let databaseName = await hash(expect.getState().currentTestName);

    const path = `/tmp/${databaseName}.sqlite`;
    appConfig.db.url = `file:${path}`;
    appConfig.db.provider = "sqlite";

    let testEnv = await setupTestEnv({ config: appConfig });
    let context = testEnv.testArgs.context;

    await testEnv.connect();

    return { context, testEnv };
}

NoTuxNoBux avatar Apr 25 '22 08:04 NoTuxNoBux