Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
87 changes: 22 additions & 65 deletions packages/runtime/test/client-api/delegate.test.ts
Original file line number Diff line number Diff line change
@@ -1,74 +1,26 @@
import path from 'node:path';
import { afterEach, beforeEach, describe, expect, it } from 'vitest';
import type { ClientContract } from '../../src';
import { schema, type SchemaType } from '../schemas/delegate/schema';
import { createTestClient } from '../utils';

const DB_NAME = `client-api-delegate-tests`;

describe.each([{ provider: 'sqlite' as const }, { provider: 'postgresql' as const }])(
'Delegate model tests for $provider',
({ provider }) => {
const POLYMORPHIC_SCHEMA = `
model User {
id Int @id @default(autoincrement())
email String? @unique
level Int @default(0)
assets Asset[]
ratedVideos RatedVideo[] @relation('direct')
}

model Comment {
id Int @id @default(autoincrement())
content String
asset Asset? @relation(fields: [assetId], references: [id], onDelete: Cascade)
assetId Int?
}

model Asset {
id Int @id @default(autoincrement())
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
viewCount Int @default(0)
owner User? @relation(fields: [ownerId], references: [id], onDelete: Cascade)
ownerId Int?
comments Comment[]
assetType String

@@delegate(assetType)
}

model Video extends Asset {
duration Int
url String @unique
videoType String

@@delegate(videoType)
}

model RatedVideo extends Video {
rating Int
user User? @relation(name: 'direct', fields: [userId], references: [id], onDelete: Cascade)
userId Int?
}

model Image extends Asset {
format String
gallery Gallery? @relation(fields: [galleryId], references: [id], onDelete: Cascade)
galleryId Int?
}

model Gallery {
id Int @id @default(autoincrement())
images Image[]
}
`;

let client: any;
let client: ClientContract<SchemaType>;

beforeEach(async () => {
client = await createTestClient(POLYMORPHIC_SCHEMA, {
usePrismaPush: true,
provider,
dbName: provider === 'postgresql' ? DB_NAME : undefined,
});
client = await createTestClient(
schema,
{
usePrismaPush: true,
provider,
dbName: provider === 'postgresql' ? DB_NAME : undefined,
},
path.join(__dirname, '../schemas/delegate/schema.zmodel'),
);
});

afterEach(async () => {
Expand All @@ -79,6 +31,7 @@ model Gallery {
it('works with create', async () => {
// delegate model cannot be created directly
await expect(
// @ts-expect-error
client.video.create({
data: {
duration: 100,
Expand All @@ -91,6 +44,7 @@ model Gallery {
client.user.create({
data: {
assets: {
// @ts-expect-error
create: { assetType: 'Video' },
},
},
Expand Down Expand Up @@ -294,7 +248,7 @@ model Gallery {
});

// omit fields
const r = await client.ratedVideo.findUnique({
const r: any = await client.ratedVideo.findUnique({
where: { id: v.id },
omit: {
viewCount: true,
Expand Down Expand Up @@ -341,8 +295,10 @@ model Gallery {
select: { id: true, assetType: true },
},
ratedVideos: {
url: true,
rating: true,
select: {
url: true,
rating: true,
},
},
},
}),
Expand Down Expand Up @@ -568,7 +524,7 @@ model Gallery {
client.video.findFirst({
where: {
comments: {
all: { content: 'c2' },
every: { content: 'c2' },
},
},
}),
Expand Down Expand Up @@ -878,6 +834,7 @@ model Gallery {

it('works with upsert', async () => {
await expect(
// @ts-expect-error
client.asset.upsert({
where: { id: 2 },
create: {
Expand Down
4 changes: 2 additions & 2 deletions packages/runtime/test/client-api/relation.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -242,7 +242,7 @@ describe.each([
});

describe.each([{ relationName: undefined }, { relationName: 'myM2M' }])(
'Implicit many-to-many relation ($relationName)',
'Implicit many-to-many relation (relation: $relationName)',
({ relationName }) => {
beforeEach(async () => {
client = await createTestClient(
Expand All @@ -269,7 +269,7 @@ describe.each([
`,
{
provider,
dbName: provider === 'sqlite' ? 'file:./dev.db' : TEST_DB,
dbName: provider === 'postgresql' ? TEST_DB : undefined,
usePrismaPush: true,
},
);
Expand Down
72 changes: 48 additions & 24 deletions packages/runtime/test/utils.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { invariant } from '@zenstackhq/common-helpers';
import { loadDocument } from '@zenstackhq/language';
import { PrismaSchemaGenerator } from '@zenstackhq/sdk';
import { generateTsSchema } from '@zenstackhq/testtools';
import { createTestProject, generateTsSchema } from '@zenstackhq/testtools';
import SQLite from 'better-sqlite3';
import { execSync } from 'node:child_process';
import fs from 'node:fs';
Expand Down Expand Up @@ -67,6 +67,7 @@ export type CreateTestClientOptions<Schema extends SchemaDef> = Omit<ClientOptio
export async function createTestClient<Schema extends SchemaDef>(
schema: Schema,
options?: CreateTestClientOptions<Schema>,
schemaFile?: string,
): Promise<ClientContract<Schema>>;
export async function createTestClient<Schema extends SchemaDef>(
schema: string,
Expand All @@ -75,35 +76,70 @@ export async function createTestClient<Schema extends SchemaDef>(
export async function createTestClient<Schema extends SchemaDef>(
schema: Schema | string,
options?: CreateTestClientOptions<Schema>,
schemaFile?: string,
): Promise<any> {
let workDir: string | undefined;
let _schema: Schema;
const provider = options?.provider ?? 'sqlite';

let dbName = options?.dbName;
const provider = options?.provider ?? 'sqlite';
if (provider === 'sqlite' && options?.usePrismaPush && !dbName) {
dbName = 'file:./test.db';
if (!dbName) {
if (provider === 'sqlite') {
dbName = './test.db';
} else {
throw new Error(`dbName is required for ${provider} provider`);
}
}

const dbUrl =
provider === 'sqlite'
? `file:${dbName}`
: `postgres://${TEST_PG_CONFIG.user}:${TEST_PG_CONFIG.password}@${TEST_PG_CONFIG.host}:${TEST_PG_CONFIG.port}/${dbName}`;

if (typeof schema === 'string') {
const generated = await generateTsSchema(schema, provider, dbName, options?.extraSourceFiles);
const generated = await generateTsSchema(schema, provider, dbUrl, options?.extraSourceFiles);
workDir = generated.workDir;
_schema = generated.schema as Schema;
// replace schema's provider
_schema = {
...generated.schema,
provider: {
type: provider,
},
} as Schema;
} else {
_schema = schema;
if (options?.extraSourceFiles) {
throw new Error('`extraSourceFiles` is not supported when schema is a SchemaDef object');
// replace schema's provider
_schema = {
...schema,
provider: {
type: provider,
},
};
workDir = await createTestProject();
if (schemaFile) {
let schemaContent = fs.readFileSync(schemaFile, 'utf-8');
if (dbUrl) {
// replace `datasource db { }` section
schemaContent = schemaContent.replace(
/datasource\s+db\s*{[^}]*}/m,
`datasource db {
provider = '${provider}'
url = '${dbUrl}'
}`,
);
}
fs.writeFileSync(path.join(workDir, 'schema.zmodel'), schemaContent);
}
}

console.log(`Work directory: ${workDir}`);

const { plugins, ...rest } = options ?? {};
const _options: ClientOptions<Schema> = {
...rest,
} as ClientOptions<Schema>;

if (options?.usePrismaPush) {
invariant(typeof schema === 'string', 'schema must be a string');
invariant(workDir, 'workDir is required');
invariant(typeof schema === 'string' || schemaFile, 'a schema file must be provided when using prisma db push');
const r = await loadDocument(path.resolve(workDir, 'schema.zmodel'));
if (!r.success) {
throw new Error(r.errors.join('\n'));
Expand Down Expand Up @@ -135,7 +171,7 @@ export async function createTestClient<Schema extends SchemaDef>(
} as unknown as ClientOptions<Schema>['dialectConfig'];
} else {
_options.dialectConfig = {
database: new SQLite(options?.usePrismaPush ? getDbPath(path.join(workDir!, 'schema.prisma')) : ':memory:'),
database: new SQLite(path.join(workDir!, dbName)),
} as unknown as ClientOptions<Schema>['dialectConfig'];
}

Expand All @@ -153,15 +189,3 @@ export async function createTestClient<Schema extends SchemaDef>(

return client;
}

function getDbPath(prismaSchemaPath: string) {
const content = fs.readFileSync(prismaSchemaPath, 'utf-8');
const found = content.match(/^\s*url\s*=(\s|")*([^"]+)(\s|")*$/m);
if (!found) {
throw new Error('No url found in prisma schema');
}
const dbPath = found[2]!;
// convert 'file:./dev.db' to './dev.db'
const r = path.join(path.dirname(prismaSchemaPath), dbPath.replace(/^file:/, ''));
return r;
}
11 changes: 5 additions & 6 deletions packages/testtools/src/schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,21 +7,21 @@ import path from 'node:path';
import { match } from 'ts-pattern';
import { createTestProject } from './project';

function makePrelude(provider: 'sqlite' | 'postgresql', dbName?: string) {
function makePrelude(provider: 'sqlite' | 'postgresql', dbUrl?: string) {
return match(provider)
.with('sqlite', () => {
return `
datasource db {
provider = 'sqlite'
url = '${dbName ?? ':memory:'}'
url = '${dbUrl ?? 'file:./test.db'}'
}
`;
})
.with('postgresql', () => {
return `
datasource db {
provider = 'postgresql'
url = 'postgres://postgres:postgres@localhost:5432/${dbName}'
url = '${dbUrl ?? 'postgres://postgres:postgres@localhost:5432/db'}'
}
`;
})
Expand All @@ -31,15 +31,14 @@ datasource db {
export async function generateTsSchema(
schemaText: string,
provider: 'sqlite' | 'postgresql' = 'sqlite',
dbName?: string,
dbUrl?: string,
extraSourceFiles?: Record<string, string>,
) {
const workDir = createTestProject();
console.log(`Work directory: ${workDir}`);

const zmodelPath = path.join(workDir, 'schema.zmodel');
const noPrelude = schemaText.includes('datasource ');
fs.writeFileSync(zmodelPath, `${noPrelude ? '' : makePrelude(provider, dbName)}\n\n${schemaText}`);
fs.writeFileSync(zmodelPath, `${noPrelude ? '' : makePrelude(provider, dbUrl)}\n\n${schemaText}`);

const pluginModelFiles = glob.sync(path.resolve(__dirname, '../../runtime/src/plugins/**/plugin.zmodel'));

Expand Down
2 changes: 1 addition & 1 deletion tests/e2e/cal.com/cal-com.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import path from 'node:path';
describe('Cal.com e2e tests', () => {
it('has a working schema', async () => {
await expect(
generateTsSchema(fs.readFileSync(path.join(__dirname, 'schema.zmodel'), 'utf8'), 'postgresql', 'cal-com'),
generateTsSchema(fs.readFileSync(path.join(__dirname, 'schema.zmodel'), 'utf8'), 'postgresql'),
).resolves.toBeTruthy();
});
});
2 changes: 1 addition & 1 deletion tests/e2e/formbricks/formbricks.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import path from 'node:path';
describe('Formbricks e2e tests', () => {
it('has a working schema', async () => {
await expect(
generateTsSchema(fs.readFileSync(path.join(__dirname, 'schema.zmodel'), 'utf8'), 'postgresql', 'cal-com'),
generateTsSchema(fs.readFileSync(path.join(__dirname, 'schema.zmodel'), 'utf8'), 'postgresql'),
).resolves.toBeTruthy();
});
});
2 changes: 1 addition & 1 deletion tests/e2e/trigger.dev/trigger-dev.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { describe, expect, it } from 'vitest';
describe('Trigger.dev e2e tests', () => {
it('has a working schema', async () => {
await expect(
generateTsSchema(fs.readFileSync(path.join(__dirname, 'schema.zmodel'), 'utf8'), 'postgresql', 'cal-com'),
generateTsSchema(fs.readFileSync(path.join(__dirname, 'schema.zmodel'), 'utf8'), 'postgresql'),
).resolves.toBeTruthy();
});
});