import { UserTeam } from "@/models"
import { CreateService } from "@/services/user-positions"
import {
organizationFactory,
positionFactory,
positionTeamFactory,
teamFactory,
userFactory,
} from "@/factories"
describe("api/src/services/user-positions/create-service.ts", () => {
describe("CreateService", () => {
describe("#perform", () => {
test("when passed valid attributes, it creates a user position", async () => {
// Arrange
const user = await userFactory.create()
const organization = await organizationFactory.create()
const position = await positionFactory.create({
organizationId: organization.id,
})
const attributes = {
userId: user.id,
positionId: position.id,
}
// Act
const userPosition = await CreateService.perform(attributes)
// Assert
expect(userPosition).toEqual(
expect.objectContaining({
userId: user.id,
positionId: position.id,
})
)
})
test("when userId is missing, errors informatively", async () => {
// Arrange
const organization = await organizationFactory.create()
const position = await positionFactory.create({
organizationId: organization.id,
})
const attributes = {
positionId: position.id,
}
// Assert
expect.assertions(1)
await expect(
// Act
CreateService.perform(attributes)
).rejects.toThrow("User ID is required")
})
})
test("when positionId is missing, errors informatively", async () => {
// Arrange
const user = await userFactory.create()
const attributes = {
userId: user.id,
}
// Assert
expect.assertions(1)
await expect(
// Act
CreateService.perform(attributes)
).rejects.toThrow("Position ID is required")
})
test("when position has teams, it creates user teams for all teams position has access to", async () => {
// Arrange
const user = await userFactory.create()
const organization = await organizationFactory.create()
const position = await positionFactory.create({
organizationId: organization.id,
})
const team1 = await teamFactory.create({
organizationId: organization.id,
})
const team2 = await teamFactory.create({
organizationId: organization.id,
})
const team3 = await teamFactory.create({
organizationId: organization.id,
})
const positionTeam1 = await positionTeamFactory.create({
teamId: team1.id,
positionId: position.id,
})
const positionTeam2 = await positionTeamFactory.create({
teamId: team2.id,
positionId: position.id,
})
const positionTeam3 = await positionTeamFactory.create({
teamId: team3.id,
positionId: position.id,
})
const attributes = {
userId: user.id,
positionId: position.id,
}
// Act
const userPosition = await CreateService.perform(attributes)
// Assert
expect(userPosition).toEqual(
expect.objectContaining({
userId: user.id,
positionId: position.id,
})
)
const userTeams = await UserTeam.findAll({
where: { userId: user.id },
order: [["teamId", "ASC"]],
})
expect(userTeams).toHaveLength(3)
expect(userTeams).toEqual([
expect.objectContaining({
userId: user.id,
teamId: team1.id,
positionId: position.id,
positionRole: positionTeam1.role,
sources: UserTeam.Sources.POSITION,
}),
expect.objectContaining({
userId: user.id,
teamId: team2.id,
positionId: position.id,
positionRole: positionTeam2.role,
sources: UserTeam.Sources.POSITION,
}),
expect.objectContaining({
userId: user.id,
teamId: team3.id,
positionId: position.id,
positionRole: positionTeam3.role,
sources: UserTeam.Sources.POSITION,
}),
])
})
test("when position has teams and user is already on a team, it creates user teams for all teams position has access to and does not duplicate existing user teams", async () => {
// Arrange
const user = await userFactory.create()
const organization = await organizationFactory.create()
const position = await positionFactory.create({
organizationId: organization.id,
})
const team1 = await teamFactory.create({
organizationId: organization.id,
})
const team2 = await teamFactory.create({
organizationId: organization.id,
})
const team3 = await teamFactory.create({
organizationId: organization.id,
})
const positionTeam1 = await positionTeamFactory.create({
teamId: team1.id,
positionId: position.id,
role: "Role 1",
})
const positionTeam2 = await positionTeamFactory.create({
teamId: team2.id,
positionId: position.id,
role: "Role 2",
})
const positionTeam3 = await positionTeamFactory.create({
teamId: team3.id,
positionId: position.id,
role: "Role 3",
})
await UserTeam.create({
userId: user.id,
teamId: team1.id,
sources: UserTeam.Sources.DIRECT,
})
await UserTeam.create({
userId: user.id,
teamId: team2.id,
sources: UserTeam.Sources.DIRECT,
})
const attributes = {
userId: user.id,
positionId: position.id,
}
// Act
const userPosition = await CreateService.perform(attributes)
// Assert
expect(userPosition).toEqual(
expect.objectContaining({
userId: user.id,
positionId: position.id,
})
)
const userTeams = await UserTeam.findAll({
where: { userId: user.id },
order: [["teamId", "ASC"]],
})
expect(userTeams).toHaveLength(3)
expect(userTeams).toEqual([
expect.objectContaining({
userId: user.id,
teamId: team1.id,
positionId: position.id,
positionRole: positionTeam1.role,
sources: UserTeam.Sources.DIRECT_AND_POSITION,
}),
expect.objectContaining({
userId: user.id,
teamId: team2.id,
positionId: position.id,
positionRole: positionTeam2.role,
sources: UserTeam.Sources.DIRECT_AND_POSITION,
}),
expect.objectContaining({
userId: user.id,
teamId: team3.id,
positionId: position.id,
positionRole: positionTeam3.role,
sources: UserTeam.Sources.POSITION,
}),
])
})
})
})
Clear and concise description of the problem
I want an
aroundEachso that I can run my database tests inside a transaction and roll back the transaction after the test completes. This would vastly speed up my test runs.Currently I need to wipe each table in my database, on each test run, to be sure that test state is clean.
Naturally, due to having a great many migrations, this is more performant than dropping the whole database and then rebuilding the schema.
code I want to replace with aroundEach wrapped in a transaction
It's also possible that this is already possible, but after spending an entire day trying to get it to work, I'm at least reasonably sure it isn't.
Suggested solution
Add support for a simple
aroundEachhook.This would vastly speed up my test runs from
To something much more reasonable.
Its true that I can do this in each test, but who wants to clutter up their test code with setup logic?
Alternative
Implement my own
aroundEachthat looks likeAdditional context
Will be used in all of the projects I'm working; pretty much anything in https://github.com/orgs/icefoganalytics/repositories?type=public (subset of https://github.com/orgs/ytgov/repositories?type=all).
Example of tests that would benefit from beforeEach transaction wrapping
Validations