redwood icon indicating copy to clipboard operation
redwood copied to clipboard

API Testing - Scenario Types

Open cjreimer opened this issue 3 years ago • 4 comments

The scenario generator works great, and even has basic typescript support!

However, when functions are embedded into the scenario creation object to support relations, the redwood typescript fails. For example, I'm trying to set up the following scenario:

export const standard = defineScenario ({
  organization: {
    orgA: (): Prisma.OrganizationCreateArgs => ({
      data: {
        name: 'Org A',
      },
    }),
  },
  user: {
    userA: (scenario): Prisma.UserCreateArgs => ({
      data: {
        nameFirst: 'Bob',
        nameLast: 'Builder',
        email: '[email protected]',
        phone: '123-456-7890',
        title: 'boss',
        roles: 'abc,def',
        active: true,
        uid: 'aaa',
        organizationId: scenario.organization.orgA.id,
      },
    }),
  },
  project: {
    projectA: (): Prisma.ProjectCreateArgs => ({
      data: {
        name: 'Project 1',
        description: 'Project 1 desc',
        owner: 'Project 1 Owner',
      },
    }),
  },

  milestone: {
    one: (scenario): Prisma.MilestoneCreateArgs => ({
      data: {
        name: 'String',
        createdById: scenario.user.userA.id,
        dateTargetActive: true,
        dateTarget: '2021-06-07T21:02:43Z',
        dateActual: '2021-06-07T21:02:43Z',
        projectId: scenario.project.projectA.id,
      },
    }),
    two: (scenario): Prisma.MilestoneCreateArgs => ({
      data: {
        name: 'String',
        createdById: scenario.user.userA.id,
        dateTargetActive: true,
        dateTarget: '2021-06-07T21:02:43Z',
        dateActual: '2021-06-10T21:02:43Z',
        projectId: scenario.project.projectA.id,
      },
    }),
  },
})

Because of the functions to resolve the relations (which is an elegant system), the typescript blows up. For now, I have resolved the situation manually by not using the Redwood defineScenario function to create the types and manually typing the seeded result.

import type { Organization, Milestone, Project, User } from '@prisma/client'

export const standard = {...}

export type StandardScenario = {
  organization: {
    orgA: Organization
  }
  user: {
    userA: User
  }
  project: {
    projectA: Project
  }
  milestone: {
    one: Milestone
    two: Milestone
  }
}

If one of our typescript gurus could find an automatic way of typing the StandardScenario type from the standard definition , that would be awesome! I took a stab at it, but this goes past my current Typescript knowledge! If for some reason this proves challenging, then at a minimum we should probably update the docs regarding the typescript support.

Thanks!

cjreimer avatar Feb 04 '22 13:02 cjreimer

@cjreimer just to confirm, in your example, the scenario is for your Organization model (e.g. the file's called organization.scenarios.ts and defineScenario initially had a Prisma generic passed to it, like defineScenario<Prisma.OrganizationCreateArgs>), but since you're creating Users and Projects in the same scenario (which is totally allowed!) the typing breaks down in two ways:

  1. the relation functions aren't being appropriately typed (which is why you have to be explicit about the return types in your example above), and
  2. the exported type StandardScenario is wrong?

jtoar avatar Feb 15 '22 04:02 jtoar

@jtoar, thanks for looking into this!

Yes, I think you got it. I'll clarify a bit more, numbered as per your comments:

  1. By default, the scaffold generator creates a scenario using the following line:

export const standard = defineScenario<Prisma.ModelCreateArgs>({

As we need to create a multi-model scenario, this definition is no longer correct. It's easy enough to get rid of the type on the defineScenario function and type each function as shown in my example above to make sure that the entry is typed correctly. It would be great if the scaffold generator could do this by default, but this would be the lower priority of the two issues in my mind, but it may be the easier issue to change.

  1. The type of the exported StandardScenario would be for me the higher priority. When using a multi-model scenario, I had to completely replace redwoods automatically generated StandardScenario with a manual one, as shown in the original post. It would be great if we could create a generic type system to create the StandardScenario type. The trick is that the resulting objects under the user property for example are no longer a Prisma.UserCreateArgs, but rather a prisma User type.

Other than for the most basic apps, I believe that multi-model scenarios are the most common use case. Note that I've also stitched together multiple model scenario files as follows:

import { standard as milestoneStandard } from ....

export const standard = {
  ...milestoneStandard,
  requirement: {...}
}

Does that clarify things?

cjreimer avatar Feb 15 '22 14:02 cjreimer

@cjreimer yes it does thank you very much!

jtoar avatar Feb 15 '22 20:02 jtoar

I think this is slightly incorrect:

export type StandardScenario = {
  organization: {
    orgA: Organization
  }
  user: {
    userA: User
  }
  project: {
    projectA: Project
  }
  milestone: {
    one: Milestone
    two: Milestone
  }
}

Couldn't we write it more precisely, generically and concisely like this?

export type StandardScenario = {
  organization: Record<string, Prisma.OrganizationCreateArgs['data']>,
  user: Record<string, Prisma.UserCreateArgs['data']>,
  project: Record<string, Prisma.ProjectCreateArgs['data']>,
  milestone: Record<string, Prisma.MilestoneCreateArgs['data']>,
}

standuprey avatar Aug 08 '22 21:08 standuprey

any update on best practices here?

This works for strongly typing named instances of a model:

const standardOrganization = {
  testOrganization: {
    data: {
      name: 'Test',
    },
  } as Prisma.OrganizationCreateArgs,
}
...
export const standard = {
  organization: standardOrganization,
  space: standardSpace,
  type: standardType,
  content: standardContent,
}

export type StandardScenario = {
  organization: Record<keyof typeof standardOrganization, Organization>
  space: Record<keyof typeof standardSpace, Space>
  type: Record<keyof typeof standardType, Type>
  content: Record<keyof typeof standardContent, Content>
}

dphuang2 avatar Sep 27 '22 20:09 dphuang2

Hi! Are there any updates on this? Running into the same issues :)

I can't even compost out the scenario data and pass them into defineScenario while having it typed, or I get a "type is possibly infinitely deep" error. Specifically, I tried doing this: Screen Shot 2022-09-30 at 5 45 36 PM

arimendelow avatar Sep 30 '22 21:09 arimendelow

Hi! Are there any updates on this? Running into the same issues :)

I can't even compost out the scenario data and pass them into defineScenario while having it typed, or I get a "type is possibly infinitely deep" error. Specifically, I tried doing this: Screen Shot 2022-09-30 at 5 45 36 PM

You don’t need defineScenario. See my example above

dphuang2 avatar Sep 30 '22 22:09 dphuang2

OH I'm not sure how I totally missed that - will certainly give it a go, curious for the best practices as well. Thanks!

arimendelow avatar Sep 30 '22 22:09 arimendelow

Wait - how does your example work with inlining functions to create relations? That's the main issue.

arimendelow avatar Sep 30 '22 22:09 arimendelow

Wait - how does your example work with inlining functions to create relations? That's the main issue.

Here is an example:

const standardSpace = {
  testSpace: (scenario: StandardScenario): Prisma.SpaceCreateArgs => ({
    data: {
      name: 'Test',
      organizationId: scenario.organization.testOrganization.id,
    },
  }),
}

dphuang2 avatar Sep 30 '22 22:09 dphuang2

This is a better solution than using defineScenario for sure, but gets extremely verbose if you're creating multiple models in each scenario and using them in other scenarios. Curious for the redwood team to weigh in on this.

arimendelow avatar Sep 30 '22 22:09 arimendelow

This is a better solution than using defineScenario for sure, but gets extremely verbose if you're creating multiple models in each scenario and using them in other scenarios. Curious for the redwood team to weigh in on this.

Repeated LOC is O(N) where N is # of models. I don’t think you can do better than that though.

on second thought, maybe O(1) is possible with some TypeScript tricks

dphuang2 avatar Sep 30 '22 22:09 dphuang2

Hi there. How about directly calling the db.[model].create or defined repository.create before the test code, without using the scenario feature?

image

bokukpark avatar Aug 11 '23 10:08 bokukpark