|
| 1 | +import { exec } from 'child_process'; |
| 2 | +import { promisify } from 'util'; |
| 3 | +import { existsSync, readFileSync } from 'fs'; |
| 4 | +import { describe, it, expect, beforeAll } from 'vitest'; |
| 5 | +import path from 'path'; |
| 6 | + |
| 7 | +const execAsync = promisify(exec); |
| 8 | + |
| 9 | +describe('Self-Relation Generation', () => { |
| 10 | + beforeAll(async () => { |
| 11 | + // Build the generator first |
| 12 | + await execAsync('npm run build'); |
| 13 | + |
| 14 | + // Generate models for self-relation schema |
| 15 | + const schemaPath = path.resolve(__dirname, 'schemas/self-relation.prisma'); |
| 16 | + await execAsync(`npx prisma generate --schema="${schemaPath}"`); |
| 17 | + }, 60000); |
| 18 | + |
| 19 | + it('should generate UserBase class without self-relations', () => { |
| 20 | + const outputPath = path.resolve(__dirname, 'generated/self-relation'); |
| 21 | + const userBasePath = path.join(outputPath, 'models', 'UserBase.model.ts'); |
| 22 | + |
| 23 | + expect(existsSync(userBasePath)).toBe(true); |
| 24 | + const userBase = readFileSync(userBasePath, 'utf-8'); |
| 25 | + |
| 26 | + // Should have scalar fields |
| 27 | + expect(userBase).toContain('id!: number'); |
| 28 | + expect(userBase).toContain('email!: string'); |
| 29 | + expect(userBase).toContain('name?: string'); |
| 30 | + expect(userBase).toContain('createdAt!: Date'); |
| 31 | + expect(userBase).toContain('mentorId?: number'); |
| 32 | + |
| 33 | + // Should have decorators |
| 34 | + expect(userBase).toContain('@IsInt()'); |
| 35 | + expect(userBase).toContain('@IsString()'); |
| 36 | + expect(userBase).toContain('@IsDate()'); |
| 37 | + expect(userBase).toContain('@IsDefined()'); |
| 38 | + expect(userBase).toContain('@IsOptional()'); |
| 39 | + |
| 40 | + // Should NOT have self-relation fields |
| 41 | + expect(userBase).not.toContain('mentor?'); |
| 42 | + expect(userBase).not.toContain('mentees'); |
| 43 | + expect(userBase).not.toContain('User[]'); |
| 44 | + }); |
| 45 | + |
| 46 | + it('should generate UserRelations class with only self-relation fields', () => { |
| 47 | + const outputPath = path.resolve(__dirname, 'generated/self-relation'); |
| 48 | + const userRelationsPath = path.join( |
| 49 | + outputPath, |
| 50 | + 'models', |
| 51 | + 'UserRelations.model.ts', |
| 52 | + ); |
| 53 | + |
| 54 | + expect(existsSync(userRelationsPath)).toBe(true); |
| 55 | + const userRelations = readFileSync(userRelationsPath, 'utf-8'); |
| 56 | + |
| 57 | + // Should have self-relation fields |
| 58 | + expect(userRelations).toContain('mentor?: User'); |
| 59 | + expect(userRelations).toContain('mentees!: User[]'); |
| 60 | + |
| 61 | + // Should have class-validator decorators |
| 62 | + expect(userRelations).toContain('@IsOptional()'); |
| 63 | + expect(userRelations).toContain('@IsDefined()'); |
| 64 | + |
| 65 | + // Should have Swagger decorators |
| 66 | + expect(userRelations).toContain('@ApiProperty({'); |
| 67 | + expect(userRelations).toContain('type: () => User'); |
| 68 | + expect(userRelations).toContain('required: false'); |
| 69 | + expect(userRelations).toContain('isArray: true'); |
| 70 | + |
| 71 | + // For self-relations in Relations class, should import from the combined model |
| 72 | + expect(userRelations).toContain('import { User } from "./User.model"'); |
| 73 | + |
| 74 | + // Should NOT have scalar fields |
| 75 | + expect(userRelations).not.toContain('id!: number'); |
| 76 | + expect(userRelations).not.toContain('email!: string'); |
| 77 | + expect(userRelations).not.toContain('mentorId'); |
| 78 | + }); |
| 79 | + |
| 80 | + it('should generate combined User class with self-relations', () => { |
| 81 | + const outputPath = path.resolve(__dirname, 'generated/self-relation'); |
| 82 | + const userModelPath = path.join(outputPath, 'models', 'User.model.ts'); |
| 83 | + |
| 84 | + expect(existsSync(userModelPath)).toBe(true); |
| 85 | + const userModel = readFileSync(userModelPath, 'utf-8'); |
| 86 | + |
| 87 | + // Should import from UserBase |
| 88 | + expect(userModel).toContain('import { UserBase } from "./UserBase.model"'); |
| 89 | + |
| 90 | + // Should extend UserBase |
| 91 | + expect(userModel).toContain('export class User extends UserBase'); |
| 92 | + |
| 93 | + // Should have self-relation fields with decorators |
| 94 | + expect(userModel).toContain('mentor?: User'); |
| 95 | + expect(userModel).toContain('mentees!: User[]'); |
| 96 | + |
| 97 | + // Should have class-validator imports |
| 98 | + expect(userModel).toContain( |
| 99 | + 'import { IsOptional, IsDefined } from "class-validator"', |
| 100 | + ); |
| 101 | + |
| 102 | + // Should have Swagger imports |
| 103 | + expect(userModel).toContain( |
| 104 | + 'import { ApiProperty } from "@nestjs/swagger"', |
| 105 | + ); |
| 106 | + |
| 107 | + // Should have decorators on relation fields |
| 108 | + expect(userModel).toContain('@IsOptional()'); |
| 109 | + expect(userModel).toContain('@IsDefined()'); |
| 110 | + expect(userModel).toContain('@ApiProperty({'); |
| 111 | + |
| 112 | + // Should NOT import User from itself (no circular import) |
| 113 | + expect(userModel).not.toContain('import { User } from "./"'); |
| 114 | + expect(userModel).not.toContain('import { User } from "./User.model"'); |
| 115 | + }); |
| 116 | + |
| 117 | + it('should handle self-relations without circular import issues', () => { |
| 118 | + const outputPath = path.resolve(__dirname, 'generated/self-relation'); |
| 119 | + |
| 120 | + // Check that the index file exports User |
| 121 | + const indexPath = path.join(outputPath, 'models', 'index.ts'); |
| 122 | + expect(existsSync(indexPath)).toBe(true); |
| 123 | + const index = readFileSync(indexPath, 'utf-8'); |
| 124 | + |
| 125 | + // When separateRelationFields is enabled, index should export all classes |
| 126 | + expect(index).toContain('export { User } from "./User.model"'); |
| 127 | + // Note: UserBase and UserRelations might not be in index if only User is exported |
| 128 | + // This depends on the generator's index generation logic |
| 129 | + }); |
| 130 | +}); |
0 commit comments