morintd / prismock

A mock for PrismaClient, dedicated to unit testing.
https://www.npmjs.com/package/prismock
MIT License
189 stars 21 forks source link

Client Extensions #564

Open ajhollowayvrm opened 1 year ago

ajhollowayvrm commented 1 year ago

This is more of a question. I use client extensions (https://www.prisma.io/docs/concepts/components/prisma-client/client-extensions) pretty heavily in my code. I can mock and recreate those on my own but does Prismock support them?

morintd commented 1 year ago

Hello @ajhollowayvrm , there is actually a dummy implementation of the $extends method in prismock, but it won't be enough if you are really using it...

I'll have a look to get a better support for $extends in the next few days/weeks. If you have a public repo with specific use-cases, let me know, I'll use it to validate my configuration.

ajhollowayvrm commented 11 months ago

export const DBConn = new PrismaClient({
    log: [
        {
            emit: 'stdout',
            level: 'error',
        },
        {
            emit: 'stdout',
            level: 'info',
        },
        {
            emit: 'stdout',
            level: 'warn',
        },
    ],
})
    .$extends({ name: 'TypeConversion Extensions', query: ClientExtensions })
    .$extends({
        name: 'User Attributes',
        result: {
            users: {
                injectUserAttributes: {
                    name: 'inject user attributes function',
                    needs: { id: true },
                    compute(user: any) {
                        return async (requestingUserCompanyID?: number) => {
                            // Determines if user_attributes are needed based on fields being returned
                            if (Object.keys(user).some((column) => UserAttributesFields.includes(column))) {
                                if (!user.hasOwnProperty('user_attributes')) {
                                    user.user_attributes = await DBConn.user_attributes.findMany({
                                        where: { user_id: user.id },
                                    });
                                }
                                await injectUserAttributes(user, requestingUserCompanyID);
                            }
                            return user;
                        };
                    },
                },
                updateAttributes: {
                    name: 'update user attributes function',
                    needs: { id: true, companyid: true, recognition_manager: true, recognition_admin: true, custom_manager_budget: true },
                    compute(data) {
                        return () =>
                            DBConn.user_attributes.update({
                                where: { user_id_company_id: { user_id: data.id, company_id: data.companyid } },
                                data: {
                                    manager: data.recognition_manager,
                                    admin: data.recognition_admin,
                                    budget: data.custom_manager_budget,
                                },
                                include: {
                                    users: true,
                                },
                            });
                    },
                },
                key: {
                    name: 'manger key',
                    needs: { first_name: true, last_name: true, username: true },
                    // used for manager in manager.controller.ts
                    compute(data) {
                        return data.first_name + ' ' + data.last_name + ' ' + data.username;
                    },
                },
            },
        },
    })
    .$extends({
        model: {
            users: {
                async exists(where) {
                    const count = await DBConn.users.count({ where });
                    return count > 0;
                },
            },
        },
    });
ajhollowayvrm commented 11 months ago

@morintd here's an example of how I use it

morintd commented 11 months ago

Thanks @ajhollowayvrm , I've been experimenting with extensions in the past few days. I don't see it getting complete support short-term, but I'll add functionalities incrementally.

I might use your example and report back about it here.

p0thi commented 2 months ago

Hey @morintd. First of all: Thanks for this awesome library! I am also using extensions for my prisma client. My use case is to add a field __caslSubjectType__, that adds the models name to each object coming from prisma. I need that for the authorization library casl (v6) so it can detect the corresponding model of the object:

import { Prisma, PrismaClient } from "@prisma/client";

const brandExtension = Prisma.defineExtension((client) => {
  type ModelKey = Exclude<keyof typeof client, `$${string}` | symbol>;
  type Result = {
    [K in ModelKey]: {
      __caslSubjectType__: { needs: object; compute: () => K };
    };
  };

  const result = {} as Result;
  const modelKeys = Object.keys(client).filter(
    (key) => !key.startsWith("$"),
  ) as ModelKey[];
  modelKeys.forEach((k) => {
    const capK = k.charAt(0).toUpperCase() + k.slice(1);
    result[k] = {
      __caslSubjectType__: { needs: {}, compute: () => capK as any },
    };
  });

  return client.$extends({ result });
});

export const prisma = new PrismaClient().$extends(brandExtension);

So if the prisma model is named User, each user has a field __caslSubjectType__: "User".

Can you think of any other good solution I could achieve the same with, without using extensions with prismock? Or even better: Get that extension to work with it? That would be great!