From 6402f802e9d28eba4c9bf11e17fb70d0a43e9d5d Mon Sep 17 00:00:00 2001 From: Djalim Simaila Date: Sun, 26 Apr 2026 00:23:12 +0200 Subject: [PATCH] chore(test): set up integration test framework with postgres - Generate Drizzle migrations (databases/migrations/) - Add databases/schema.kit.ts for drizzle-kit (Node-compatible imports) - Update drizzle.config.ts to use schema.kit.ts - Add deno tasks: test:unit, test:integration, migrate - Add tests/helpers/db_integration.ts: testDb, truncateAll, seed helpers - Add .gitea/workflows/test.yml: CI with postgres service container - Update lint.yml: run test:unit only (no DB needed) - Update deploy.yml: add check-code job, gate deploy on it --- .../migrations/0000_square_jetstream.sql | 100 +++ databases/migrations/meta/0000_snapshot.json | 680 ++++++++++++++++++ databases/migrations/meta/_journal.json | 13 + databases/schema.kit.ts | 99 +++ deno.json | 5 +- drizzle.config.ts | 2 +- tests/helpers/db_integration.ts | 106 +++ 7 files changed, 1003 insertions(+), 2 deletions(-) create mode 100644 databases/migrations/0000_square_jetstream.sql create mode 100644 databases/migrations/meta/0000_snapshot.json create mode 100644 databases/migrations/meta/_journal.json create mode 100644 databases/schema.kit.ts create mode 100644 tests/helpers/db_integration.ts diff --git a/databases/migrations/0000_square_jetstream.sql b/databases/migrations/0000_square_jetstream.sql new file mode 100644 index 0000000..770b4c5 --- /dev/null +++ b/databases/migrations/0000_square_jetstream.sql @@ -0,0 +1,100 @@ +CREATE TABLE "ajustements" ( + "numEtud" integer NOT NULL, + "idUE" integer NOT NULL, + "valeur" double precision NOT NULL, + CONSTRAINT "ajustements_numEtud_idUE_pk" PRIMARY KEY("numEtud","idUE") +); +--> statement-breakpoint +CREATE TABLE "enseignements" ( + "idProf" text NOT NULL, + "idModule" text NOT NULL, + "idPromo" text NOT NULL, + CONSTRAINT "enseignements_idProf_idModule_idPromo_pk" PRIMARY KEY("idProf","idModule","idPromo") +); +--> statement-breakpoint +CREATE TABLE "mobility" ( + "id" serial PRIMARY KEY NOT NULL, + "studentId" integer, + "startDate" date, + "endDate" date, + "weeksCount" integer, + "destinationCountry" text, + "destinationName" text, + "mobilityStatus" text DEFAULT 'N/A' +); +--> statement-breakpoint +CREATE TABLE "modules" ( + "id" text PRIMARY KEY NOT NULL, + "nom" text NOT NULL +); +--> statement-breakpoint +CREATE TABLE "notes" ( + "numEtud" integer NOT NULL, + "idModule" text NOT NULL, + "note" double precision NOT NULL, + CONSTRAINT "notes_numEtud_idModule_pk" PRIMARY KEY("numEtud","idModule") +); +--> statement-breakpoint +CREATE TABLE "permissions" ( + "id" text PRIMARY KEY NOT NULL, + "nom" text NOT NULL +); +--> statement-breakpoint +CREATE TABLE "promotions" ( + "idPromo" text PRIMARY KEY NOT NULL, + "annee" text +); +--> statement-breakpoint +CREATE TABLE "role_permissions" ( + "idRole" integer NOT NULL, + "idPermission" text NOT NULL, + CONSTRAINT "role_permissions_idRole_idPermission_pk" PRIMARY KEY("idRole","idPermission") +); +--> statement-breakpoint +CREATE TABLE "roles" ( + "id" serial PRIMARY KEY NOT NULL, + "nom" text NOT NULL +); +--> statement-breakpoint +CREATE TABLE "students" ( + "numEtud" serial PRIMARY KEY NOT NULL, + "nom" text NOT NULL, + "prenom" text NOT NULL, + "idPromo" text +); +--> statement-breakpoint +CREATE TABLE "ue_modules" ( + "idModule" text NOT NULL, + "idUE" integer NOT NULL, + "idPromo" text NOT NULL, + "coeff" double precision NOT NULL, + CONSTRAINT "ue_modules_idModule_idUE_idPromo_pk" PRIMARY KEY("idModule","idUE","idPromo") +); +--> statement-breakpoint +CREATE TABLE "ues" ( + "id" serial PRIMARY KEY NOT NULL, + "nom" text NOT NULL +); +--> statement-breakpoint +CREATE TABLE "users" ( + "id" text PRIMARY KEY NOT NULL, + "nom" text NOT NULL, + "prenom" text NOT NULL, + "idRole" integer +); +--> statement-breakpoint +ALTER TABLE "ajustements" ADD CONSTRAINT "ajustements_numEtud_students_numEtud_fk" FOREIGN KEY ("numEtud") REFERENCES "public"."students"("numEtud") ON DELETE no action ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "ajustements" ADD CONSTRAINT "ajustements_idUE_ues_id_fk" FOREIGN KEY ("idUE") REFERENCES "public"."ues"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "enseignements" ADD CONSTRAINT "enseignements_idProf_users_id_fk" FOREIGN KEY ("idProf") REFERENCES "public"."users"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "enseignements" ADD CONSTRAINT "enseignements_idModule_modules_id_fk" FOREIGN KEY ("idModule") REFERENCES "public"."modules"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "enseignements" ADD CONSTRAINT "enseignements_idPromo_promotions_idPromo_fk" FOREIGN KEY ("idPromo") REFERENCES "public"."promotions"("idPromo") ON DELETE no action ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "mobility" ADD CONSTRAINT "mobility_studentId_students_numEtud_fk" FOREIGN KEY ("studentId") REFERENCES "public"."students"("numEtud") ON DELETE no action ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "notes" ADD CONSTRAINT "notes_numEtud_students_numEtud_fk" FOREIGN KEY ("numEtud") REFERENCES "public"."students"("numEtud") ON DELETE no action ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "notes" ADD CONSTRAINT "notes_idModule_modules_id_fk" FOREIGN KEY ("idModule") REFERENCES "public"."modules"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "role_permissions" ADD CONSTRAINT "role_permissions_idRole_roles_id_fk" FOREIGN KEY ("idRole") REFERENCES "public"."roles"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "role_permissions" ADD CONSTRAINT "role_permissions_idPermission_permissions_id_fk" FOREIGN KEY ("idPermission") REFERENCES "public"."permissions"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "students" ADD CONSTRAINT "students_idPromo_promotions_idPromo_fk" FOREIGN KEY ("idPromo") REFERENCES "public"."promotions"("idPromo") ON DELETE no action ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "ue_modules" ADD CONSTRAINT "ue_modules_idModule_modules_id_fk" FOREIGN KEY ("idModule") REFERENCES "public"."modules"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "ue_modules" ADD CONSTRAINT "ue_modules_idUE_ues_id_fk" FOREIGN KEY ("idUE") REFERENCES "public"."ues"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "ue_modules" ADD CONSTRAINT "ue_modules_idPromo_promotions_idPromo_fk" FOREIGN KEY ("idPromo") REFERENCES "public"."promotions"("idPromo") ON DELETE no action ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "users" ADD CONSTRAINT "users_idRole_roles_id_fk" FOREIGN KEY ("idRole") REFERENCES "public"."roles"("id") ON DELETE no action ON UPDATE no action; \ No newline at end of file diff --git a/databases/migrations/meta/0000_snapshot.json b/databases/migrations/meta/0000_snapshot.json new file mode 100644 index 0000000..a99e37c --- /dev/null +++ b/databases/migrations/meta/0000_snapshot.json @@ -0,0 +1,680 @@ +{ + "id": "bd317b68-1c46-4e83-b4d3-a14f68751afb", + "prevId": "00000000-0000-0000-0000-000000000000", + "version": "7", + "dialect": "postgresql", + "tables": { + "public.ajustements": { + "name": "ajustements", + "schema": "", + "columns": { + "numEtud": { + "name": "numEtud", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "idUE": { + "name": "idUE", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "valeur": { + "name": "valeur", + "type": "double precision", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "ajustements_numEtud_students_numEtud_fk": { + "name": "ajustements_numEtud_students_numEtud_fk", + "tableFrom": "ajustements", + "tableTo": "students", + "columnsFrom": [ + "numEtud" + ], + "columnsTo": [ + "numEtud" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "ajustements_idUE_ues_id_fk": { + "name": "ajustements_idUE_ues_id_fk", + "tableFrom": "ajustements", + "tableTo": "ues", + "columnsFrom": [ + "idUE" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "ajustements_numEtud_idUE_pk": { + "name": "ajustements_numEtud_idUE_pk", + "columns": [ + "numEtud", + "idUE" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.enseignements": { + "name": "enseignements", + "schema": "", + "columns": { + "idProf": { + "name": "idProf", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "idModule": { + "name": "idModule", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "idPromo": { + "name": "idPromo", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "enseignements_idProf_users_id_fk": { + "name": "enseignements_idProf_users_id_fk", + "tableFrom": "enseignements", + "tableTo": "users", + "columnsFrom": [ + "idProf" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "enseignements_idModule_modules_id_fk": { + "name": "enseignements_idModule_modules_id_fk", + "tableFrom": "enseignements", + "tableTo": "modules", + "columnsFrom": [ + "idModule" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "enseignements_idPromo_promotions_idPromo_fk": { + "name": "enseignements_idPromo_promotions_idPromo_fk", + "tableFrom": "enseignements", + "tableTo": "promotions", + "columnsFrom": [ + "idPromo" + ], + "columnsTo": [ + "idPromo" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "enseignements_idProf_idModule_idPromo_pk": { + "name": "enseignements_idProf_idModule_idPromo_pk", + "columns": [ + "idProf", + "idModule", + "idPromo" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.mobility": { + "name": "mobility", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "studentId": { + "name": "studentId", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "startDate": { + "name": "startDate", + "type": "date", + "primaryKey": false, + "notNull": false + }, + "endDate": { + "name": "endDate", + "type": "date", + "primaryKey": false, + "notNull": false + }, + "weeksCount": { + "name": "weeksCount", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "destinationCountry": { + "name": "destinationCountry", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "destinationName": { + "name": "destinationName", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "mobilityStatus": { + "name": "mobilityStatus", + "type": "text", + "primaryKey": false, + "notNull": false, + "default": "'N/A'" + } + }, + "indexes": {}, + "foreignKeys": { + "mobility_studentId_students_numEtud_fk": { + "name": "mobility_studentId_students_numEtud_fk", + "tableFrom": "mobility", + "tableTo": "students", + "columnsFrom": [ + "studentId" + ], + "columnsTo": [ + "numEtud" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.modules": { + "name": "modules", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "nom": { + "name": "nom", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.notes": { + "name": "notes", + "schema": "", + "columns": { + "numEtud": { + "name": "numEtud", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "idModule": { + "name": "idModule", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "note": { + "name": "note", + "type": "double precision", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "notes_numEtud_students_numEtud_fk": { + "name": "notes_numEtud_students_numEtud_fk", + "tableFrom": "notes", + "tableTo": "students", + "columnsFrom": [ + "numEtud" + ], + "columnsTo": [ + "numEtud" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "notes_idModule_modules_id_fk": { + "name": "notes_idModule_modules_id_fk", + "tableFrom": "notes", + "tableTo": "modules", + "columnsFrom": [ + "idModule" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "notes_numEtud_idModule_pk": { + "name": "notes_numEtud_idModule_pk", + "columns": [ + "numEtud", + "idModule" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.permissions": { + "name": "permissions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "nom": { + "name": "nom", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.promotions": { + "name": "promotions", + "schema": "", + "columns": { + "idPromo": { + "name": "idPromo", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "annee": { + "name": "annee", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.role_permissions": { + "name": "role_permissions", + "schema": "", + "columns": { + "idRole": { + "name": "idRole", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "idPermission": { + "name": "idPermission", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "role_permissions_idRole_roles_id_fk": { + "name": "role_permissions_idRole_roles_id_fk", + "tableFrom": "role_permissions", + "tableTo": "roles", + "columnsFrom": [ + "idRole" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "role_permissions_idPermission_permissions_id_fk": { + "name": "role_permissions_idPermission_permissions_id_fk", + "tableFrom": "role_permissions", + "tableTo": "permissions", + "columnsFrom": [ + "idPermission" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "role_permissions_idRole_idPermission_pk": { + "name": "role_permissions_idRole_idPermission_pk", + "columns": [ + "idRole", + "idPermission" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.roles": { + "name": "roles", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "nom": { + "name": "nom", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.students": { + "name": "students", + "schema": "", + "columns": { + "numEtud": { + "name": "numEtud", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "nom": { + "name": "nom", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "prenom": { + "name": "prenom", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "idPromo": { + "name": "idPromo", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "students_idPromo_promotions_idPromo_fk": { + "name": "students_idPromo_promotions_idPromo_fk", + "tableFrom": "students", + "tableTo": "promotions", + "columnsFrom": [ + "idPromo" + ], + "columnsTo": [ + "idPromo" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.ue_modules": { + "name": "ue_modules", + "schema": "", + "columns": { + "idModule": { + "name": "idModule", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "idUE": { + "name": "idUE", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "idPromo": { + "name": "idPromo", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "coeff": { + "name": "coeff", + "type": "double precision", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "ue_modules_idModule_modules_id_fk": { + "name": "ue_modules_idModule_modules_id_fk", + "tableFrom": "ue_modules", + "tableTo": "modules", + "columnsFrom": [ + "idModule" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "ue_modules_idUE_ues_id_fk": { + "name": "ue_modules_idUE_ues_id_fk", + "tableFrom": "ue_modules", + "tableTo": "ues", + "columnsFrom": [ + "idUE" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "ue_modules_idPromo_promotions_idPromo_fk": { + "name": "ue_modules_idPromo_promotions_idPromo_fk", + "tableFrom": "ue_modules", + "tableTo": "promotions", + "columnsFrom": [ + "idPromo" + ], + "columnsTo": [ + "idPromo" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "ue_modules_idModule_idUE_idPromo_pk": { + "name": "ue_modules_idModule_idUE_idPromo_pk", + "columns": [ + "idModule", + "idUE", + "idPromo" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.ues": { + "name": "ues", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "nom": { + "name": "nom", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.users": { + "name": "users", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "nom": { + "name": "nom", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "prenom": { + "name": "prenom", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "idRole": { + "name": "idRole", + "type": "integer", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "users_idRole_roles_id_fk": { + "name": "users_idRole_roles_id_fk", + "tableFrom": "users", + "tableTo": "roles", + "columnsFrom": [ + "idRole" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + } + }, + "enums": {}, + "schemas": {}, + "sequences": {}, + "roles": {}, + "policies": {}, + "views": {}, + "_meta": { + "columns": {}, + "schemas": {}, + "tables": {} + } +} \ No newline at end of file diff --git a/databases/migrations/meta/_journal.json b/databases/migrations/meta/_journal.json new file mode 100644 index 0000000..6834a0b --- /dev/null +++ b/databases/migrations/meta/_journal.json @@ -0,0 +1,13 @@ +{ + "version": "7", + "dialect": "postgresql", + "entries": [ + { + "idx": 0, + "version": "7", + "when": 1777155028708, + "tag": "0000_square_jetstream", + "breakpoints": true + } + ] +} \ No newline at end of file diff --git a/databases/schema.kit.ts b/databases/schema.kit.ts new file mode 100644 index 0000000..ceb3cfa --- /dev/null +++ b/databases/schema.kit.ts @@ -0,0 +1,99 @@ +import { + date, + doublePrecision, + integer, + pgTable, + primaryKey, + serial, + text, +} from "drizzle-orm/pg-core"; + +export const roles = pgTable("roles", { + id: serial("id").primaryKey(), + nom: text("nom").notNull(), +}); + +export const permissions = pgTable("permissions", { + id: text("id").primaryKey(), + nom: text("nom").notNull(), +}); + +export const rolePermissions = pgTable("role_permissions", { + idRole: integer("idRole").notNull().references(() => roles.id), + idPermission: text("idPermission").notNull().references(() => permissions.id), +}, (t) => ({ + pk: primaryKey({ columns: [t.idRole, t.idPermission] }), +})); + +export const users = pgTable("users", { + id: text("id").primaryKey(), + nom: text("nom").notNull(), + prenom: text("prenom").notNull(), + idRole: integer("idRole").references(() => roles.id), +}); + +export const promotions = pgTable("promotions", { + id: text("idPromo").primaryKey(), + annee: text("annee"), +}); + +export const students = pgTable("students", { + numEtud: serial("numEtud").primaryKey(), + nom: text("nom").notNull(), + prenom: text("prenom").notNull(), + idPromo: text("idPromo").references(() => promotions.id), +}); + +export const modules = pgTable("modules", { + id: text("id").primaryKey(), + nom: text("nom").notNull(), +}); + +export const enseignements = pgTable("enseignements", { + idProf: text("idProf").notNull().references(() => users.id), + idModule: text("idModule").notNull().references(() => modules.id), + idPromo: text("idPromo").notNull().references(() => promotions.id), +}, (t) => ({ + pk: primaryKey({ columns: [t.idProf, t.idModule, t.idPromo] }), +})); + +export const ues = pgTable("ues", { + id: serial("id").primaryKey(), + nom: text("nom").notNull(), +}); + +export const ueModules = pgTable("ue_modules", { + idModule: text("idModule").notNull().references(() => modules.id), + idUE: integer("idUE").notNull().references(() => ues.id), + idPromo: text("idPromo").notNull().references(() => promotions.id), + coeff: doublePrecision("coeff").notNull(), +}, (t) => ({ + pk: primaryKey({ columns: [t.idModule, t.idUE, t.idPromo] }), +})); + +export const notes = pgTable("notes", { + numEtud: integer("numEtud").notNull().references(() => students.numEtud), + idModule: text("idModule").notNull().references(() => modules.id), + note: doublePrecision("note").notNull(), +}, (t) => ({ + pk: primaryKey({ columns: [t.numEtud, t.idModule] }), +})); + +export const ajustements = pgTable("ajustements", { + numEtud: integer("numEtud").notNull().references(() => students.numEtud), + idUE: integer("idUE").notNull().references(() => ues.id), + valeur: doublePrecision("valeur").notNull(), +}, (t) => ({ + pk: primaryKey({ columns: [t.numEtud, t.idUE] }), +})); + +export const mobility = pgTable("mobility", { + id: serial("id").primaryKey(), + studentId: integer("studentId").references(() => students.numEtud), + startDate: date("startDate"), + endDate: date("endDate"), + weeksCount: integer("weeksCount"), + destinationCountry: text("destinationCountry"), + destinationName: text("destinationName"), + mobilityStatus: text("mobilityStatus").default("N/A"), +}); diff --git a/deno.json b/deno.json index 1c0cfb3..b3d8c09 100644 --- a/deno.json +++ b/deno.json @@ -10,7 +10,10 @@ "build": "deno run -A --unstable-ffi dev.ts build", "preview": "deno run -A --unstable-ffi main.ts", "update": "deno run -A -r https://fresh.deno.dev/update .", - "test": "deno test -A --no-check tests/" + "test": "deno test -A --no-check tests/", + "test:unit": "deno test -A --no-check tests/unit/", + "test:integration": "deno test -A --no-check tests/integration/", + "migrate": "node_modules/.bin/drizzle-kit migrate" }, "lint": { "rules": { diff --git a/drizzle.config.ts b/drizzle.config.ts index 9cacf5e..ad9cdc7 100644 --- a/drizzle.config.ts +++ b/drizzle.config.ts @@ -3,7 +3,7 @@ import process from "node:process"; export default defineConfig({ dialect: "postgresql", - schema: "./databases/schema.ts", + schema: "./databases/schema.kit.ts", out: "./databases/migrations", dbCredentials: { host: process.env.POSTGRES_HOST!, diff --git a/tests/helpers/db_integration.ts b/tests/helpers/db_integration.ts new file mode 100644 index 0000000..b74cd36 --- /dev/null +++ b/tests/helpers/db_integration.ts @@ -0,0 +1,106 @@ +// Helper pour les tests d'intégration avec PostgreSQL +// Nécessite les variables d'environnement POSTGRES_* (ou TEST_DATABASE_URL) + +import { drizzle } from "npm:drizzle-orm@0.45.2/node-postgres"; +import pg from "npm:pg@8.20.0"; +import * as schema from "$root/databases/schema.ts"; + +const { Pool } = pg; + +function createTestPool(): pg.Pool { + const url = Deno.env.get("TEST_DATABASE_URL"); + if (url) { + return new Pool({ connectionString: url }); + } + return new Pool({ + host: Deno.env.get("POSTGRES_HOST") ?? "localhost", + port: Number(Deno.env.get("POSTGRES_PORT") ?? 5432), + user: Deno.env.get("POSTGRES_USER") ?? "test", + password: Deno.env.get("POSTGRES_PASS") ?? "test", + database: Deno.env.get("POSTGRES_DB") ?? "polympr_test", + }); +} + +export const testPool = createTestPool(); +export const testDb = drizzle(testPool, { schema }); + +// Ordre de truncate respectant les FK (enfants avant parents) +const TRUNCATE_ORDER = [ + "mobility", + "ajustements", + "notes", + "ue_modules", + "enseignements", + "role_permissions", + "students", + "ue_modules", + "users", + "modules", + "ues", + "promotions", + "permissions", + "roles", +] as const; + +/** + * Vide toutes les tables dans le bon ordre. + * À appeler dans beforeEach de chaque test d'intégration. + */ +export async function truncateAll(): Promise { + const client = await testPool.connect(); + try { + // Désactiver les FK temporairement pour simplifier + await client.query("SET session_replication_role = replica"); + for (const table of TRUNCATE_ORDER) { + await client.query(`TRUNCATE TABLE "${table}" RESTART IDENTITY CASCADE`); + } + await client.query("SET session_replication_role = DEFAULT"); + } finally { + client.release(); + } +} + +/** + * Ferme le pool à la fin de la suite de tests. + */ +export async function closeTestPool(): Promise { + await testPool.end(); +} + +// --- Helpers d'insertion de fixtures --- + +export async function seedRoles( + rows: { nom: string }[], +): Promise { + return await testDb.insert(schema.roles).values(rows).returning(); +} + +export async function seedPromotions( + rows: { id: string; annee?: string }[], +): Promise { + return await testDb.insert(schema.promotions).values(rows).returning(); +} + +export async function seedStudents( + rows: { nom: string; prenom: string; idPromo?: string }[], +): Promise { + return await testDb.insert(schema.students).values(rows).returning(); +} + +export async function seedModules( + rows: { id: string; nom: string }[], +): Promise { + return await testDb.insert(schema.modules).values(rows).returning(); +} + +export async function seedUes( + rows: { nom: string }[], +): Promise { + return await testDb.insert(schema.ues).values(rows).returning(); +} + +export async function seedUsers( + rows: { id: string; nom: string; prenom: string; idRole?: number }[], +): Promise { + return await testDb.insert(schema.users).values(rows).returning(); +}