Quramy / jest-prisma

Jest environment for integrated testing with Prisma client
MIT License
274 stars 16 forks source link

If Date Object input is specified as DateTime type gte, lte, lt,gt operator in include's where condition doesn't work #56

Open tkow opened 1 year ago

tkow commented 1 year ago

I'm sorry if it's already known, but I can't input Date JS object as DateTime type fields at where operators using jestPrisma.client in my environment though original prisma client passes tests.

where: {
                  created_at: {
                    gte: {},
                    ~~~
                    lt: {}
                    ~~~~
                  }
},

The photo shows running first test with prisma-jest, and second with original client.

スクリーンショット 2022-12-10 23 17 17

Version Spec

## node
node: v16.15.0

## DB
Postgresql

## packages
"@quramy/jest-prisma": "^1.3.1",
"jest": "^29.3.1",
"ts-jest": "^29.0.3",
"ts-loader": "^8.0.4",

## prisma
prisma                  : 4.7.1
@prisma/client          : 4.7.1
Current platform        : darwin
Query Engine (Node-API) : libquery-engine 272861e07ab64f234d3ffc4094e32bd61775599c (at node_modules/@prisma/engines/libquery_engine-darwin.dylib.node)
Migration Engine        : migration-engine-cli 272861e07ab64f234d3ffc4094e32bd61775599c (at node_modules/@prisma/engines/migration-engine-darwin)
Introspection Engine    : introspection-core 272861e07ab64f234d3ffc4094e32bd61775599c (at node_modules/@prisma/engines/introspection-engine-darwin)
Format Binary           : prisma-fmt 272861e07ab64f234d3ffc4094e32bd61775599c (at node_modules/@prisma/engines/prisma-fmt-darwin)
Format Wasm             : @prisma/prisma-fmt-wasm 4.7.1-1.272861e07ab64f234d3ffc4094e32bd61775599c
Default Engines Hash    : 272861e07ab64f234d3ffc4094e32bd61775599c
Studio                  : 0.477.0

(Addition) ~It doesn't reproduce it only just to add created_at Datetime @default(now()) to example-prj' user model.~ I try to find how to reproduce it asap.

I could reproduce it when condition has include: { where: { [$dateFiledName]: { lt: new Date } } }, so on. Both sqlite and postgresql are reproduced so it may have the problem in js layer .

// This is your Prisma schema file,
// learn more about it in the docs: https://pris.ly/d/prisma-schema

generator client {
  provider        = "prisma-client-js"
  previewFeatures = ["clientExtensions"]
}

datasource db {
  provider = "sqlite"
  url      = env("DATABASE_URL")
}

model User {
  id    String @id
  name  String
  posts Post[]
  createdAt DateTime @default(now())
}

model Post {
  id       String @id
  title    String
  author   User   @relation(fields: [authorId], references: [id])
  authorId String
  createdAt DateTime @default(now())
}
/**
 *
 * @jest-environment-options { "verboseQuery": true }
 *
 */
import { PrismaClient } from "@prisma/client";

describe("Should include date type work around", () => {

  /* NG */
  const prisma = jestPrisma.client;
  /* NG */
  //  const prisma = jestPrisma.originalClient;
  /* OK */
  // const prisma = new PrismaClient();

  beforeEach(async () => {
    await prisma.post.create({
      data: {
        id: "post0",
        title: "post",
        author: {
          create: {
            id: "user0",
            name: "quramy",
          },
        },
      },
    });
  });

  test("include api should work using date type condition", async () => {
    const user = await prisma.user.findFirst({
      where: {
        createdAt: {
          lt: new Date(),
          gte: new Date(new Date().getTime() - 1000 * 60 * 60 * 24),
        },
      },
      include: {
        posts: {
          where: {
            createdAt: {
              lt: new Date(),
              gte: new Date(new Date().getTime() - 1000 * 60 * 60 * 24),
            },
          },
        },
      },
    });

    expect(
      (await prisma.post.findFirst({
        where: {
          author: {
            createdAt: {
              lt: new Date(),
              gte: new Date(new Date().getTime() - 1000 * 60 * 60 * 24),
            },
          },
        },
        include: {
          author: {
            include: {
              posts: {
                where: {
                  createdAt: {
                    lt: new Date(),
                    gte: new Date(new Date().getTime() - 1000 * 60 * 60 * 24),
                  },
                },
              },
            },
          },
        },
      }))!.author,
    ).toStrictEqual(user);
  });
});

I confirmed that internal jest-prisma-core originalClient and jest-prisma jestPrisma.originalClient works by force creating and fetch data with overwriting in node_modules folder. In addition, I noticed that validate method in prisma calls with same data, but slightly different though I don't know why. It may be clues.

Original client

スクリーンショット 2022-12-11 13 37 08

Proxy client(both jestPrisma.client and jestPrisma.originalClient)

スクリーンショット 2022-12-11 13 36 57

I also found invalidChildren' value exists in node_modules/@prisma/client/runtime/index.js validate method with jest-prisma proxy though manually PrismaClient in jest test case doesn't.

Quramy commented 1 year ago

jest-prisma does not touch Date class. I think this is not jest-prisma issue but Jest issue.

tkow commented 1 year ago

@Quramy I think so, too. I could read all your code in this repository and I know nothing fancy process PrismaClient. Your work is very awesome and I want to use this in our product. So, I keep trying to find the reason. I found objectToArgs in prisma client method args(initialObj) are different so I try to understand where this difference comes from. Proxy client's initialObj of include-where parameter becomes empty object.

I also found first inputed values are same in every cases.

~It may mean prisma have problem it parse nested date object in different ways though it's not related to this issue.~ Sorry, this is my fault. I call toISOString() in my code.

Proxy client スクリーンショット 2022-12-11 15 08 17

Prisma using in Jest directely スクリーンショット 2022-12-11 15 09 09

It may be related to https://github.com/facebook/jest/issues/7246.

tkow commented 1 year ago

I found inserting this.global.Date = Date to setup function in environment entrypoint fix this issue. But, jest may extend it for inspecting Date with matcher or each environment, so I don't know whether this fix is appropriate.

    async setup() {
        const jestPrisma = await this.delegate.preSetup();
        await super.setup();
        this.global.jestPrisma = jestPrisma;
        this.global.Date = Date
    }
tkow commented 1 year ago

@Quramy I create the PR and global.Date is not overwritten in it, because I don't know how large the influence is. As I thought it, jestPrisma exports Date constructor from same environment is most safety way. In addition the Date class doesn't have problem as long as using only it as prisma api argument, because it run only environment set up scope. Please check it if you feel like it.

Quramy commented 1 year ago

@tkow Thanks for your investigating and sending the PR.

        this.global.Date = Date

I think this problem is caused by https://github.com/facebook/jest/issues/2549 ( It's famous jest issue, and tooooo long to read 😭 )

And 3rd party jest environments to tackle the issue are published:

e.g. https://www.npmjs.com/package/jest-environment-node-single-context

For now, I don't know whether jest-prisma should extend the "workaround" environment or not.

But users can weave jest-prisma function to their own environment like this:

import type { Circus } from "@jest/types";
import type { JestEnvironmentConfig, EnvironmentContext } from "@jest/environment";

import { PrismaEnvironmentDelegate } from "@quramy/jest-prisma-core";
import Environment from "jest-environment-node-single-context";

export default class PrismaEnvironment extends Environment {
  private readonly delegate: PrismaEnvironmentDelegate;

  constructor(config: JestEnvironmentConfig, context: EnvironmentContext) {
    super(config, context);
    this.delegate = new PrismaEnvironmentDelegate(config, context);
  }

  async setup() {
    const jestPrisma = await this.delegate.preSetup();
    await super.setup();
    this.global.jestPrisma = jestPrisma;
  }

  handleTestEvent(event: Circus.Event) {
    return this.delegate.handleTestEvent(event);
  }

  async teardown() {
    await Promise.all([super.teardown(), this.delegate.teardown()]);
  }
}
  1. Would you try the above example and confirm to solve your date le / lte / ge / gte problem?
tkow commented 1 year ago

@Quramy Thank you for checking and telling me why it happens. I tested your patch in example application overwrite jest-prisma environment, and it works. (For I did asap, I force ts file to be transpiled to js and the compiled file set to jest environement. So I'l remove the branch near future. (https://github.com/tkow/jest-prisma/pull/1/files) (pass test) So, what's the way to resolve this you prefer? At least, I think in anyway this library seem to need some patches for testing all prisma api.

As I thought it, jest-prisma includes jest-environment-node-single-context relatively better than the others in the point of whether we can test intuitively (no attention and extra config). If you are busy, may I reimplement this referring to the patch?

Quramy commented 1 year ago

Thanks for reply and testing.

So, what's the way to resolve this you prefer? At least, I think in anyway this library seem to need some patches for testing all prisma api

Hummm, I'm thinking about this. Please give me some time before I answer the question.

tkow commented 1 year ago

Of course. I bypass this pitfall by extending environment now as you told me. Thank you.