diff --git a/.gitignore b/.gitignore index ed4ee5f..9a72b07 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,6 @@ node_modules/ reports/ -build/ +dist/ generated/prisma/ ### JetBrains template diff --git a/cucumber.js b/cucumber.js new file mode 100644 index 0000000..a3fe61c --- /dev/null +++ b/cucumber.js @@ -0,0 +1,9 @@ +let common = [ + "features/**/*.feature", + "--require-module ts-node/register", + "--require features/**/*.ts", +].join(" "); + +module.exports = { + default: common, +}; diff --git a/cucumber.json b/cucumber.json deleted file mode 100644 index e04e983..0000000 --- a/cucumber.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "default": { - "loader": [ - "ts-node/esm" - ], - "import": [ - "features/**/*.ts" - ] - } -} diff --git a/eslint.config.js b/eslint.config.mjs similarity index 81% rename from eslint.config.js rename to eslint.config.mjs index 87336bd..2f63a14 100644 --- a/eslint.config.js +++ b/eslint.config.mjs @@ -1,10 +1,10 @@ import eslint from "@eslint/js"; -import prettierConfig from "eslint-config-prettier"; import tseslint from "typescript-eslint"; +import prettierConfig from "eslint-config-prettier"; export default tseslint.config( { - ignores: ["build/**"], + ignores: ["dist/**", "node_modules/**", "**/*.js"], }, eslint.configs.recommended, tseslint.configs.recommended, diff --git a/features/steps/common_steps.ts b/features/steps/common_steps.ts index e5f1788..214ef86 100644 --- a/features/steps/common_steps.ts +++ b/features/steps/common_steps.ts @@ -1,10 +1,10 @@ import { Given } from "@cucumber/cucumber"; -import { User } from "../../src/Domain/Models/User.js"; -import { Vehicle } from "../../src/Domain/Models/Vehicle.js"; -import { InMemoryFleetRepository } from "../../src/Secondary/Repositories/InMemoryFleetRepository.js"; -import { initializeFleetForUser } from "./shared/initializeFleetForUser.js"; -import { registerVehicleInUserFleet } from "./register_vehicle_steps.js"; -import { generateFrenchPlateNumber } from "../../tests/Utils/generateFrenchPlateNumber.js"; +import { User } from "../../src/Domain/Models/User"; +import { Vehicle } from "../../src/Domain/Models/Vehicle"; +import { InMemoryFleetRepository } from "../../src/Infra/Secondary/Repositories/InMemoryFleetRepository"; +import { initializeFleetForUser } from "./shared/initializeFleetForUser"; +import { registerVehicleInUserFleet } from "./register_vehicle_steps"; +import { generateFrenchPlateNumber } from "../../tests/Utils/generateFrenchPlateNumber"; Given("my fleet", async function (): Promise { const user: User = User.create(crypto.randomUUID()); diff --git a/features/steps/park_vehicle_steps.ts b/features/steps/park_vehicle_steps.ts index 4ac34da..62e3727 100644 --- a/features/steps/park_vehicle_steps.ts +++ b/features/steps/park_vehicle_steps.ts @@ -3,12 +3,12 @@ import { Given, Then, When } from "@cucumber/cucumber"; import { expect } from "chai"; // Second group: Domain -import { Location } from "../../src/Domain/Models/Location.js"; -import { VehicleAlreadyParkedAtThisLocationError } from "../../src/Domain/Errors/VehicleAlreadyParkedAtThisLocationError.js"; +import { Location } from "../../src/Domain/Models/Location"; +import { VehicleAlreadyParkedAtThisLocationError } from "../../src/Domain/Errors/VehicleAlreadyParkedAtThisLocationError"; // Third group: Helpers -import { parkVehicleAtLocation } from "./shared/parkVehicleAtLocation.js"; -import { retrieveLocation } from "./shared/retrieveLocation.js"; +import { parkVehicleAtLocation } from "./shared/parkVehicleAtLocation"; +import { retrieveLocation } from "./shared/retrieveLocation"; Given("a location", async function (): Promise { this.context.location = Location.create(48.8566, 2.3522); @@ -71,9 +71,7 @@ Then( const expected = new VehicleAlreadyParkedAtThisLocationError( this.context.vehicle.plateNumber, this.context.fleetId, - this.context.location.latitude, - this.context.location.longitude, - this.context.location.altitude, + this.context.location, ); expect(this.context.parkingAttemptError).to.deep.equal(expected); diff --git a/features/steps/register_vehicle_steps.ts b/features/steps/register_vehicle_steps.ts index 7691bef..d56d6d0 100644 --- a/features/steps/register_vehicle_steps.ts +++ b/features/steps/register_vehicle_steps.ts @@ -4,17 +4,17 @@ import { World } from "cucumber"; import { expect } from "chai"; // Second group: Domain -import { User } from "../../src/Domain/Models/User.js"; -import { Vehicle } from "../../src/Domain/Models/Vehicle.js"; -import { VehicleAlreadyRegisteredError } from "../../src/Domain/Errors/VehicleAlreadyRegisteredError.js"; +import { User } from "../../src/Domain/Models/User"; +import { Vehicle } from "../../src/Domain/Models/Vehicle"; +import { VehicleAlreadyRegisteredError } from "../../src/Domain/Errors/VehicleAlreadyRegisteredError"; // Third group: Helpers -import { initializeFleetForUser } from "./shared/initializeFleetForUser.js"; -import { retrieveFleet } from "./shared/retrieveFleet.js"; +import { initializeFleetForUser } from "./shared/initializeFleetForUser"; +import { retrieveFleet } from "./shared/retrieveFleet"; import { RegisterVehicle, RegisterVehicleHandler, -} from "../../src/App/Commands/registerVehicle.js"; +} from "../../src/App/Commands/registerVehicle"; Given("the fleet of another user", async function (): Promise { this.context.otherUser = User.create(crypto.randomUUID()); diff --git a/features/steps/shared/initializeFleetForUser.ts b/features/steps/shared/initializeFleetForUser.ts index d66e419..89300f1 100644 --- a/features/steps/shared/initializeFleetForUser.ts +++ b/features/steps/shared/initializeFleetForUser.ts @@ -1,9 +1,9 @@ -import { FleetRepository } from "../../../src/Domain/Repositories/FleetRepository.js"; -import { User } from "../../../src/Domain/Models/User.js"; +import { FleetRepository } from "../../../src/Domain/Ports/FleetRepository"; import { InitializeFleet, InitializeFleetHandler, -} from "../../../src/App/Commands/initializeFleet.js"; +} from "../../../src/App/Commands/initializeFleet"; +import { User } from "../../../src/Domain/Models/User"; export async function initializeFleetForUser( repository: FleetRepository, diff --git a/features/steps/shared/parkVehicleAtLocation.ts b/features/steps/shared/parkVehicleAtLocation.ts index ff9a31f..65f720a 100644 --- a/features/steps/shared/parkVehicleAtLocation.ts +++ b/features/steps/shared/parkVehicleAtLocation.ts @@ -1,9 +1,9 @@ -import { Location } from "../../../src/Domain/Models/Location.js"; -import { FleetRepository } from "../../../src/Domain/Repositories/FleetRepository.js"; +import { Location } from "../../../src/Domain/Models/Location"; +import { FleetRepository } from "../../../src/Domain/Ports/FleetRepository"; import { ParkVehicle, ParkVehicleHandler, -} from "../../../src/App/Commands/parkVehicle.js"; +} from "../../../src/App/Commands/parkVehicle"; export async function parkVehicleAtLocation( repository: FleetRepository, diff --git a/features/steps/shared/retrieveFleet.ts b/features/steps/shared/retrieveFleet.ts index 96e0829..7836253 100644 --- a/features/steps/shared/retrieveFleet.ts +++ b/features/steps/shared/retrieveFleet.ts @@ -1,9 +1,6 @@ -import { Fleet } from "../../../src/Domain/Models/Fleet.js"; -import { FleetRepository } from "../../../src/Domain/Repositories/FleetRepository.js"; -import { - GetFleet, - GetFleetHandler, -} from "../../../src/App/Queries/getFleet.js"; +import { Fleet } from "../../../src/Domain/Models/Fleet"; +import { FleetRepository } from "../../../src/Domain/Ports/FleetRepository"; +import { GetFleet, GetFleetHandler } from "../../../src/App/Queries/getFleet"; export async function retrieveFleet( repository: FleetRepository, diff --git a/features/steps/shared/retrieveLocation.ts b/features/steps/shared/retrieveLocation.ts index ea8478e..086db26 100644 --- a/features/steps/shared/retrieveLocation.ts +++ b/features/steps/shared/retrieveLocation.ts @@ -1,9 +1,9 @@ -import { Location } from "../../../src/Domain/Models/Location.js"; -import { FleetRepository } from "../../../src/Domain/Repositories/FleetRepository.js"; +import { Location } from "../../../src/Domain/Models/Location"; +import { FleetRepository } from "../../../src/Domain/Ports/FleetRepository"; import { GetLocation, GetLocationHandler, -} from "../../../src/App/Queries/getLocation.js"; +} from "../../../src/App/Queries/getLocation"; export async function retrieveLocation( repository: FleetRepository, diff --git a/package-lock.json b/package-lock.json index 656f0fb..a8920aa 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,13 +9,13 @@ "version": "1.0.0", "license": "ISC", "dependencies": { + "@eslint/js": "^9.27.0", "@prisma/client": "^6.8.2", "chalk": "^5.4.1", "yargs": "^17.7.2" }, "devDependencies": { "@cucumber/cucumber": "^11.2.0", - "@eslint/js": "^9.25.1", "@types/chai": "^5.2.1", "@types/cucumber": "^6.0.1", "@types/node": "^22.14.1", @@ -26,7 +26,6 @@ "prettier": "^3.5.3", "prisma": "^6.8.2", "ts-node": "^10.9.2", - "tsx": "^4.19.4", "typescript": "^5.8.3", "typescript-eslint": "^8.31.0", "vitest": "^3.1.2" @@ -927,13 +926,15 @@ } }, "node_modules/@eslint/js": { - "version": "9.26.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.26.0.tgz", - "integrity": "sha512-I9XlJawFdSMvWjDt6wksMCrgns5ggLNfFwFvnShsleWruvXM514Qxk8V246efTw+eo9JABvVz+u3q2RiAowKxQ==", - "dev": true, + "version": "9.27.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.27.0.tgz", + "integrity": "sha512-G5JD9Tu5HJEu4z2Uo4aHY2sLV64B7CDMXxFzqzjl3NKd6RVzSXNoE80jk7Y0lJkTTkjiIhBAqmlYwjuBY3tvpA==", "license": "MIT", "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://eslint.org/donate" } }, "node_modules/@eslint/object-schema": { @@ -2844,6 +2845,16 @@ "url": "https://opencollective.com/eslint" } }, + "node_modules/eslint/node_modules/@eslint/js": { + "version": "9.26.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.26.0.tgz", + "integrity": "sha512-I9XlJawFdSMvWjDt6wksMCrgns5ggLNfFwFvnShsleWruvXM514Qxk8V246efTw+eo9JABvVz+u3q2RiAowKxQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, "node_modules/eslint/node_modules/chalk": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", @@ -3355,6 +3366,8 @@ "integrity": "sha512-kGzZ3LWWQcGIAmg6iWvXn0ei6WDtV26wzHRMwDSzmAbcXrTEXxHy6IehI6/4eT6VRKyMP1eF1VqwrVUmE/LR7A==", "dev": true, "license": "MIT", + "optional": true, + "peer": true, "dependencies": { "resolve-pkg-maps": "^1.0.0" }, @@ -4703,6 +4716,8 @@ "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==", "dev": true, "license": "MIT", + "optional": true, + "peer": true, "funding": { "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" } @@ -5548,6 +5563,8 @@ "integrity": "sha512-gK5GVzDkJK1SI1zwHf32Mqxf2tSJkNx+eYcNly5+nHvWqXUJYUkWBQtKauoESz3ymezAI++ZwT855x5p5eop+Q==", "dev": true, "license": "MIT", + "optional": true, + "peer": true, "dependencies": { "esbuild": "~0.25.0", "get-tsconfig": "^4.7.5" diff --git a/package.json b/package.json index 864e114..a770653 100644 --- a/package.json +++ b/package.json @@ -1,16 +1,15 @@ { - "type": "module", "name": "fulll-backend", "version": "1.0.0", "license": "ISC", "dependencies": { + "@eslint/js": "^9.27.0", "@prisma/client": "^6.8.2", "chalk": "^5.4.1", "yargs": "^17.7.2" }, "devDependencies": { "@cucumber/cucumber": "^11.2.0", - "@eslint/js": "^9.25.1", "@types/chai": "^5.2.1", "@types/cucumber": "^6.0.1", "@types/node": "^22.14.1", @@ -21,7 +20,6 @@ "prettier": "^3.5.3", "prisma": "^6.8.2", "ts-node": "^10.9.2", - "tsx": "^4.19.4", "typescript": "^5.8.3", "typescript-eslint": "^8.31.0", "vitest": "^3.1.2" @@ -29,9 +27,9 @@ "scripts": { "generate": "prisma generate --no-hints", "build": "tsc", - "prepare": "npm run generate && npm run build", - "fleet": "npm run prepare && tsx ./build/src/cli.js", - "cucumber": "cucumber-js --config cucumber.json", + "prepare": "rm -rf dist/ && npm run generate && npm run build", + "fleet": "npm run prepare && ts-node ./dist/src/cli.js", + "cucumber": "cucumber-js", "cucumber:critical": "cucumber-js --tags \"@critical\"", "lint": "eslint .", "test": "vitest" diff --git a/src/App/Commands/initializeFleet.ts b/src/App/Commands/initializeFleet.ts index f42fe60..74a368e 100644 --- a/src/App/Commands/initializeFleet.ts +++ b/src/App/Commands/initializeFleet.ts @@ -1,6 +1,6 @@ -import { Command, CommandHandler } from "./command.js"; -import { Fleet } from "../../Domain/Models/Fleet.js"; -import { FleetRepository } from "../../Domain/Repositories/FleetRepository.js"; +import { Command, CommandHandler } from "./command"; +import { Fleet } from "../../Domain/Models/Fleet"; +import { FleetRepository } from "../../Domain/Ports/FleetRepository"; export class InitializeFleet implements Command { constructor(public readonly userId: string) {} diff --git a/src/App/Commands/parkVehicle.ts b/src/App/Commands/parkVehicle.ts index 6f81d9b..f64274f 100644 --- a/src/App/Commands/parkVehicle.ts +++ b/src/App/Commands/parkVehicle.ts @@ -1,7 +1,7 @@ -import { Command, CommandHandler } from "./command.js"; -import { Location } from "../../Domain/Models/Location.js"; -import { FleetRepository } from "../../Domain/Repositories/FleetRepository.js"; -import { FleetNotFoundError } from "../../Domain/Errors/FleetNotFoundError.js"; +import { Command, CommandHandler } from "./command"; +import { Location } from "../../Domain/Models/Location"; +import { FleetNotFoundError } from "../../Domain/Errors/FleetNotFoundError"; +import { FleetRepository } from "../../Domain/Ports/FleetRepository"; export class ParkVehicle implements Command { constructor( diff --git a/src/App/Commands/registerVehicle.ts b/src/App/Commands/registerVehicle.ts index 46fbf94..d76cff7 100644 --- a/src/App/Commands/registerVehicle.ts +++ b/src/App/Commands/registerVehicle.ts @@ -1,6 +1,6 @@ -import { Command, CommandHandler } from "./command.js"; -import { FleetRepository } from "../../Domain/Repositories/FleetRepository.js"; -import { FleetNotFoundError } from "../../Domain/Errors/FleetNotFoundError.js"; +import { Command, CommandHandler } from "./command"; +import { FleetNotFoundError } from "../../Domain/Errors/FleetNotFoundError"; +import { FleetRepository } from "../../Domain/Ports/FleetRepository"; export class RegisterVehicle implements Command { constructor( diff --git a/src/App/Queries/getFleet.ts b/src/App/Queries/getFleet.ts index e3386b4..a4e181c 100644 --- a/src/App/Queries/getFleet.ts +++ b/src/App/Queries/getFleet.ts @@ -1,7 +1,7 @@ -import { Query, QueryHandler } from "./query.js"; -import { Fleet } from "../../Domain/Models/Fleet.js"; -import { FleetRepository } from "../../Domain/Repositories/FleetRepository.js"; -import { FleetNotFoundError } from "../../Domain/Errors/FleetNotFoundError.js"; +import { Query, QueryHandler } from "./query"; +import { Fleet } from "../../Domain/Models/Fleet"; +import { FleetNotFoundError } from "../../Domain/Errors/FleetNotFoundError"; +import { FleetRepository } from "../../Domain/Ports/FleetRepository"; export class GetFleet implements Query { constructor(public readonly fleetId: string) {} diff --git a/src/App/Queries/getLocation.ts b/src/App/Queries/getLocation.ts index a95e35f..f14a9eb 100644 --- a/src/App/Queries/getLocation.ts +++ b/src/App/Queries/getLocation.ts @@ -1,7 +1,7 @@ -import { Query, QueryHandler } from "./query.js"; -import { Location } from "../../Domain/Models/Location.js"; -import { FleetRepository } from "../../Domain/Repositories/FleetRepository.js"; -import { FleetNotFoundError } from "../../Domain/Errors/FleetNotFoundError.js"; +import { Query, QueryHandler } from "./query"; +import { Location } from "../../Domain/Models/Location"; +import { FleetNotFoundError } from "../../Domain/Errors/FleetNotFoundError"; +import { FleetRepository } from "../../Domain/Ports/FleetRepository"; export class GetLocation implements Query { constructor( diff --git a/src/Domain/Errors/VehicleAlreadyParkedAtThisLocationError.ts b/src/Domain/Errors/VehicleAlreadyParkedAtThisLocationError.ts index 88bbdba..9efc616 100644 --- a/src/Domain/Errors/VehicleAlreadyParkedAtThisLocationError.ts +++ b/src/Domain/Errors/VehicleAlreadyParkedAtThisLocationError.ts @@ -1,13 +1,9 @@ +import { Location } from "../Models/Location.js"; + export class VehicleAlreadyParkedAtThisLocationError extends Error { - constructor( - vehiclePlateNumber: string, - fleetId: string, - latitude: number, - longitude: number, - altitude: number, - ) { + constructor(vehiclePlateNumber: string, fleetId: string, location: Location) { super( - `Error: Vehicle ${vehiclePlateNumber} of ${fleetId} is already parked at latitude: ${latitude}, longitude: ${longitude}, altitude: ${altitude}`, + `Error: Vehicle ${vehiclePlateNumber} of ${fleetId} is already parked at latitude: ${location.latitude}, longitude: ${location.longitude}, altitude: ${location.altitude}`, ); this.name = "VehicleAlreadyParkedAtThisLocationError"; } diff --git a/src/Domain/Models/Fleet.ts b/src/Domain/Models/Fleet.ts index f0a34f4..87ec03c 100644 --- a/src/Domain/Models/Fleet.ts +++ b/src/Domain/Models/Fleet.ts @@ -1,15 +1,15 @@ -import { Location } from "./Location.js"; -import { Vehicle } from "./Vehicle.js"; -import { VehicleAlreadyRegisteredError } from "../Errors/VehicleAlreadyRegisteredError.js"; -import { VehiclePlateNotFoundError } from "../Errors/VehiclePlateNotFoundError.js"; -import { LocationNotFoundError } from "../Errors/LocationNotFoundError.js"; +import { Location } from "./Location"; +import { Vehicle } from "./Vehicle"; +import { VehicleAlreadyRegisteredError } from "../Errors/VehicleAlreadyRegisteredError"; +import { VehiclePlateNotFoundError } from "../Errors/VehiclePlateNotFoundError"; +import { LocationNotFoundError } from "../Errors/LocationNotFoundError"; // Aggregate root export class Fleet { private constructor( public readonly id: string, public readonly userId: string, - public vehicles: Vehicle[], + public readonly vehicles: Vehicle[], ) {} static create(id: string, userId: string, vehicles: Vehicle[]): Fleet { @@ -17,35 +17,50 @@ export class Fleet { } registerVehicle(vehiclePlateNumber: string): void { - const existingVehicle = this.vehicles.find( - (v: Vehicle) => v.plateNumber === vehiclePlateNumber, - ); + this.ensureVehicleIsNotAlreadyRegistered(vehiclePlateNumber); - if (existingVehicle) - throw new VehicleAlreadyRegisteredError(existingVehicle.plateNumber); - - const vehicle = Vehicle.create(vehiclePlateNumber); - - this.vehicles.push(vehicle); + this.addVehicle(vehiclePlateNumber); } - parkVehicle(plateNumber: string, location: Location) { - const vehicle = this.findVehicleByPlateNumber(plateNumber); + parkVehicle(plateNumber: string, location: Location): void { + const vehicle = this.findVehicleByPlateNumberOrThrow(plateNumber); vehicle.parkVehicle(location, this.id); } localizeVehicle(plateNumber: string): Location { - const vehicle = this.findVehicleByPlateNumber(plateNumber); + const vehicle = this.findVehicleByPlateNumberOrThrow(plateNumber); + + return this.findLocationOrThrow(vehicle, plateNumber); + } + + private findLocationOrThrow(vehicle: Vehicle, plateNumber: string): Location { if (!vehicle.location) throw new LocationNotFoundError(plateNumber); return vehicle.location; } - private findVehicleByPlateNumber(plateNumber: string): Vehicle { - const vehicle = this.vehicles.find((v) => v.plateNumber === plateNumber); + private addVehicle(vehiclePlateNumber: string): void { + const vehicle = Vehicle.create(vehiclePlateNumber); + + this.vehicles.push(vehicle); + } + + private ensureVehicleIsNotAlreadyRegistered(vehiclePlateNumber: string) { + const existingVehicle = this.findVehicleByPlateNumber(vehiclePlateNumber); + + if (existingVehicle) + throw new VehicleAlreadyRegisteredError(existingVehicle.plateNumber); + } + + private findVehicleByPlateNumberOrThrow(plateNumber: string): Vehicle { + const vehicle = this.findVehicleByPlateNumber(plateNumber); if (!vehicle) throw new VehiclePlateNotFoundError(plateNumber); return vehicle; } + + private findVehicleByPlateNumber(plateNumber: string): Vehicle | undefined { + return this.vehicles.find((v) => v.plateNumber === plateNumber); + } } diff --git a/src/Domain/Models/Vehicle.ts b/src/Domain/Models/Vehicle.ts index 310ad10..57e7cb0 100644 --- a/src/Domain/Models/Vehicle.ts +++ b/src/Domain/Models/Vehicle.ts @@ -1,5 +1,5 @@ -import { Location } from "./Location.js"; -import { VehicleAlreadyParkedAtThisLocationError } from "../Errors/VehicleAlreadyParkedAtThisLocationError.js"; +import { Location } from "./Location"; +import { VehicleAlreadyParkedAtThisLocationError } from "../Errors/VehicleAlreadyParkedAtThisLocationError"; export class Vehicle { private constructor( @@ -11,16 +11,21 @@ export class Vehicle { return new Vehicle(plateNumber, location); } - parkVehicle(other: Location, fleetId: string): void { - if (this.location && this.location.equals(other)) + parkVehicle(location: Location, fleetId: string): void { + this.ensureVehicleIsNotAlreadyParkedAt(location, fleetId); + + this.location = location; + } + + private ensureVehicleIsNotAlreadyParkedAt( + location: Location, + fleetId: string, + ): void { + if (this.location && this.location.equals(location)) throw new VehicleAlreadyParkedAtThisLocationError( this.plateNumber, fleetId, - other.latitude, - other.longitude, - other.altitude, + location, ); - - this.location = other; } } diff --git a/src/Domain/Repositories/FleetRepository.ts b/src/Domain/Ports/FleetRepository.ts similarity index 77% rename from src/Domain/Repositories/FleetRepository.ts rename to src/Domain/Ports/FleetRepository.ts index f337cfb..0d359be 100644 --- a/src/Domain/Repositories/FleetRepository.ts +++ b/src/Domain/Ports/FleetRepository.ts @@ -1,4 +1,4 @@ -import { Fleet } from "../Models/Fleet.js"; +import { Fleet } from "../Models/Fleet"; // Secondary Port export interface FleetRepository { diff --git a/src/Primary/CLI/create.ts b/src/Infra/Primary/CLI/create.ts similarity index 90% rename from src/Primary/CLI/create.ts rename to src/Infra/Primary/CLI/create.ts index 17dbd2c..b4696bb 100644 --- a/src/Primary/CLI/create.ts +++ b/src/Infra/Primary/CLI/create.ts @@ -1,9 +1,9 @@ import { CommandModule } from "yargs"; -import { PrismaFleetRepository } from "../../Secondary/Repositories/PrismaFleetRepository.js"; +import { PrismaFleetRepository } from "../../Secondary/Repositories/PrismaFleetRepository"; import { InitializeFleet, InitializeFleetHandler, -} from "../../App/Commands/initializeFleet.js"; +} from "../../../App/Commands/initializeFleet"; export const createFleetCommand: CommandModule = { command: "create ", diff --git a/src/Primary/CLI/localizeVehicleCommand.ts b/src/Infra/Primary/CLI/localizeVehicleCommand.ts similarity index 94% rename from src/Primary/CLI/localizeVehicleCommand.ts rename to src/Infra/Primary/CLI/localizeVehicleCommand.ts index a41ab72..2b9560d 100644 --- a/src/Primary/CLI/localizeVehicleCommand.ts +++ b/src/Infra/Primary/CLI/localizeVehicleCommand.ts @@ -1,9 +1,9 @@ import { CommandModule } from "yargs"; -import { PrismaFleetRepository } from "../../Secondary/Repositories/PrismaFleetRepository.js"; +import { PrismaFleetRepository } from "../../Secondary/Repositories/PrismaFleetRepository"; import { GetLocation, GetLocationHandler, -} from "../../App/Queries/getLocation.js"; +} from "../../../App/Queries/getLocation"; export const localizeVehicleCommand: CommandModule = { command: "localize-vehicle ", diff --git a/src/Primary/CLI/registerVehicleCommand.ts b/src/Infra/Primary/CLI/registerVehicleCommand.ts similarity index 93% rename from src/Primary/CLI/registerVehicleCommand.ts rename to src/Infra/Primary/CLI/registerVehicleCommand.ts index 5d621f8..d656f80 100644 --- a/src/Primary/CLI/registerVehicleCommand.ts +++ b/src/Infra/Primary/CLI/registerVehicleCommand.ts @@ -1,9 +1,9 @@ import { CommandModule } from "yargs"; -import { PrismaFleetRepository } from "../../Secondary/Repositories/PrismaFleetRepository.js"; +import { PrismaFleetRepository } from "../../Secondary/Repositories/PrismaFleetRepository"; import { RegisterVehicle, RegisterVehicleHandler, -} from "../../App/Commands/registerVehicle.js"; +} from "../../../App/Commands/registerVehicle"; export const registerVehicleCommand: CommandModule = { command: "register-vehicle ", diff --git a/src/Primary/CLI/updateLocationCommand.ts b/src/Infra/Primary/CLI/updateLocationCommand.ts similarity index 92% rename from src/Primary/CLI/updateLocationCommand.ts rename to src/Infra/Primary/CLI/updateLocationCommand.ts index a33247f..12a282d 100644 --- a/src/Primary/CLI/updateLocationCommand.ts +++ b/src/Infra/Primary/CLI/updateLocationCommand.ts @@ -1,10 +1,10 @@ import { CommandModule } from "yargs"; -import { Location } from "../../Domain/Models/Location.js"; -import { PrismaFleetRepository } from "../../Secondary/Repositories/PrismaFleetRepository.js"; +import { Location } from "../../../Domain/Models/Location"; +import { PrismaFleetRepository } from "../../Secondary/Repositories/PrismaFleetRepository"; import { ParkVehicle, ParkVehicleHandler, -} from "../../App/Commands/parkVehicle.js"; +} from "../../../App/Commands/parkVehicle"; export const updateLocationCommand: CommandModule = { command: "update-location [alt]", diff --git a/src/Secondary/Repositories/InMemoryFleetRepository.ts b/src/Infra/Secondary/Repositories/InMemoryFleetRepository.ts similarity index 71% rename from src/Secondary/Repositories/InMemoryFleetRepository.ts rename to src/Infra/Secondary/Repositories/InMemoryFleetRepository.ts index 50de129..bf3efbb 100644 --- a/src/Secondary/Repositories/InMemoryFleetRepository.ts +++ b/src/Infra/Secondary/Repositories/InMemoryFleetRepository.ts @@ -1,5 +1,5 @@ -import { Fleet } from "../../Domain/Models/Fleet.js"; -import { FleetRepository } from "../../Domain/Repositories/FleetRepository.js"; +import { Fleet } from "../../../Domain/Models/Fleet"; +import { FleetRepository } from "../../../Domain/Ports/FleetRepository"; // Secondary Adapter export class InMemoryFleetRepository implements FleetRepository { diff --git a/src/Infra/Secondary/Repositories/Mappers/LocationMapper.ts b/src/Infra/Secondary/Repositories/Mappers/LocationMapper.ts new file mode 100644 index 0000000..17e846f --- /dev/null +++ b/src/Infra/Secondary/Repositories/Mappers/LocationMapper.ts @@ -0,0 +1,19 @@ +import { Location } from "../../../../Domain/Models/Location"; + +export class LocationMapper { + static toPrisma(location: Location): string { + return JSON.stringify({ + latitude: location.latitude, + longitude: location.longitude, + altitude: location.altitude, + }); + } + + static toDomain(prismaString: string | null): Location | undefined { + if (prismaString === null || prismaString === "") return undefined; + + const parsed = JSON.parse(prismaString); + + return Location.create(parsed.latitude, parsed.longitude, parsed.altitude); + } +} diff --git a/src/Secondary/Mappers/VehicleMapper.ts b/src/Infra/Secondary/Repositories/Mappers/VehicleMapper.ts similarity index 66% rename from src/Secondary/Mappers/VehicleMapper.ts rename to src/Infra/Secondary/Repositories/Mappers/VehicleMapper.ts index bfe291e..1432dc4 100644 --- a/src/Secondary/Mappers/VehicleMapper.ts +++ b/src/Infra/Secondary/Repositories/Mappers/VehicleMapper.ts @@ -1,6 +1,6 @@ -import { Location } from "../../Domain/Models/Location.js"; -import { Vehicle } from "../../Domain/Models/Vehicle.js"; -import { LocationMapper } from "./LocationMapper.js"; +import { Location } from "../../../../Domain/Models/Location"; +import { Vehicle } from "../../../../Domain/Models/Vehicle"; +import { LocationMapper } from "./LocationMapper"; export class VehicleMapper { static fromPrisma(vehicle: { diff --git a/src/Secondary/Repositories/PrismaFleetRepository.ts b/src/Infra/Secondary/Repositories/PrismaFleetRepository.ts similarity index 87% rename from src/Secondary/Repositories/PrismaFleetRepository.ts rename to src/Infra/Secondary/Repositories/PrismaFleetRepository.ts index 3705348..c532c08 100644 --- a/src/Secondary/Repositories/PrismaFleetRepository.ts +++ b/src/Infra/Secondary/Repositories/PrismaFleetRepository.ts @@ -1,9 +1,9 @@ -import { prisma } from "../client.js"; +import { prisma } from "../client"; import { Vehicle as PrismaVehicle } from "@prisma/client"; -import { Fleet } from "../../Domain/Models/Fleet.js"; -import { Vehicle } from "../../Domain/Models/Vehicle.js"; -import { FleetRepository } from "../../Domain/Repositories/FleetRepository.js"; -import { LocationMapper } from "../Mappers/LocationMapper.js"; +import { Fleet } from "../../../Domain/Models/Fleet"; +import { Vehicle } from "../../../Domain/Models/Vehicle"; +import { FleetRepository } from "../../../Domain/Ports/FleetRepository"; +import { LocationMapper } from "./Mappers/LocationMapper"; // Secondary Adapter export class PrismaFleetRepository implements FleetRepository { diff --git a/src/Secondary/client.ts b/src/Infra/Secondary/client.ts similarity index 100% rename from src/Secondary/client.ts rename to src/Infra/Secondary/client.ts diff --git a/src/Secondary/Mappers/LocationMapper.ts b/src/Secondary/Mappers/LocationMapper.ts deleted file mode 100644 index 8a1d048..0000000 --- a/src/Secondary/Mappers/LocationMapper.ts +++ /dev/null @@ -1,35 +0,0 @@ -import { Location } from "../../Domain/Models/Location.js"; - -export class LocationMapper { - static toPrisma(location: Location): string { - return JSON.stringify({ - latitude: location.latitude, - longitude: location.longitude, - altitude: location.altitude, - }); - } - - static toDomain(prismaString: string | null): Location | undefined { - if (prismaString === null || prismaString === "") return undefined; - try { - const parsed = JSON.parse(prismaString); - - if ( - typeof parsed.latitude === "number" && - typeof parsed.longitude === "number" && - typeof parsed.altitude === "number" - ) { - return Location.create( - parsed.latitude, - parsed.longitude, - parsed.altitude, - ); - } - - console.warn("Invalid location format:", parsed); - } catch (error) { - console.warn("Failed to parse location:", prismaString, error); - return undefined; - } - } -} diff --git a/src/cli.ts b/src/cli.ts index 2982dd2..e8ea755 100644 --- a/src/cli.ts +++ b/src/cli.ts @@ -3,10 +3,10 @@ import yargs from "yargs"; import { hideBin } from "yargs/helpers"; import chalk from "chalk"; -import { createFleetCommand } from "./Primary/CLI/create.js"; -import { registerVehicleCommand } from "./Primary/CLI/registerVehicleCommand.js"; -import { updateLocationCommand } from "./Primary/CLI/updateLocationCommand.js"; -import { localizeVehicleCommand } from "./Primary/CLI/localizeVehicleCommand.js"; +import { createFleetCommand } from "./Infra/Primary/CLI/create"; +import { registerVehicleCommand } from "./Infra/Primary/CLI/registerVehicleCommand"; +import { updateLocationCommand } from "./Infra/Primary/CLI/updateLocationCommand"; +import { localizeVehicleCommand } from "./Infra/Primary/CLI/localizeVehicleCommand"; const rawArgs = hideBin(process.argv); diff --git a/tests/Component/fleet.test.ts b/tests/Component/fleet.test.ts index 752f7dd..3945e61 100644 --- a/tests/Component/fleet.test.ts +++ b/tests/Component/fleet.test.ts @@ -1,9 +1,9 @@ import { beforeEach, describe, expect, it } from "vitest"; import { execSync } from "node:child_process"; -import { resetDB } from "../Infra/Secondary/resetDB.js"; +import { resetDB } from "../Infra/Secondary/resetDB"; const test = (args: string) => { - return execSync(`node ./build/src/cli.js ${args}`).toString(); + return execSync(`node ./dist/src/cli.js ${args}`).toString(); }; describe("Fleet", () => { @@ -12,35 +12,45 @@ describe("Fleet", () => { }); it("Nominal case", async (): Promise => { - const fleetId = test("create 2228").trim(); + const fleetId = createFleet(); expect(fleetId.length).toBeGreaterThan(0); - const registerResponse = test(`register-vehicle ${fleetId} AB-123-CD`); + const registerResponse = registerVehicle(fleetId); expect(registerResponse).toContain(fleetId); expect(registerResponse).toContain("AB-123-CD"); - const updateLocationResponse = test( - `update-location ${fleetId} AB-123-CD 48.8566 2.3522 10`, - ); + const updateLocationResponse = parkVehicle(fleetId); expect(updateLocationResponse).toContain(fleetId); expect(updateLocationResponse).toContain("AB-123-CD"); expect(updateLocationResponse).toContain("latitude: 48.8566"); expect(updateLocationResponse).toContain("longitude: 2.3522"); - const localizeVehicleResponse = test( - `localize-vehicle ${fleetId} AB-123-CD`, - ); + const localizeVehicleResponse = localizeVehicle(fleetId); expect(localizeVehicleResponse).toContain(fleetId); expect(localizeVehicleResponse).toContain("AB-123-CD"); expect(localizeVehicleResponse).toContain("latitude: 48.8566"); expect(localizeVehicleResponse).toContain("longitude: 2.3522"); expect(localizeVehicleResponse).toContain("altitude: 10"); - const updateLocationAgainResponse = test( - `update-location ${fleetId} AB-123-CD 48.8566 2.3522 10`, - ); + const updateLocationAgainResponse = parkVehicle(fleetId); expect(updateLocationAgainResponse).toContain( `Error: Vehicle AB-123-CD of ${fleetId} is already parked at latitude: 48.8566, longitude: 2.3522, altitude: 10`, ); }); + + function createFleet() { + return test("create 2228").trim(); + } + + function registerVehicle(fleetId: string) { + return test(`register-vehicle ${fleetId} AB-123-CD`); + } + + function parkVehicle(fleetId: string) { + return test(`update-location ${fleetId} AB-123-CD 48.8566 2.3522 10`); + } + + function localizeVehicle(fleetId: string) { + return test(`localize-vehicle ${fleetId} AB-123-CD`); + } }); diff --git a/tests/Domain/location.test.ts b/tests/Domain/location.test.ts index d4f7306..a30975d 100644 --- a/tests/Domain/location.test.ts +++ b/tests/Domain/location.test.ts @@ -4,20 +4,12 @@ import { Location } from "../../src/Domain/Models/Location"; describe("Location", () => { it("should create a location without altitude", (): void => { const location = Location.create(48.8566, 2.3522); - expect(location).to.be.instanceOf(Location); - }); - - it("should create a location with altitude", (): void => { - const location = Location.create(48.8566, 2.3522, 100); - expect(location).to.be.instanceOf(Location); + expect(location.altitude).toBe(0); }); it("should be equal to another location's latitude, longitude, altitude", (): void => { - const anotherLocation = Location.create(48.8566, 2.3522, 100); - const isSameLocation = Location.create(48.8566, 2.3522, 100).equals( - anotherLocation, - ); - - expect(isSameLocation).toBeTruthy(); + const aLocation = Location.create(48.8566, 2.3522, 100); + const sameLocation = Location.create(48.8566, 2.3522, 100); + expect(sameLocation.equals(aLocation)).toBeTruthy(); }); }); diff --git a/tests/Domain/user.test.ts b/tests/Domain/user.test.ts deleted file mode 100644 index 5d1c457..0000000 --- a/tests/Domain/user.test.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { describe, expect, it } from "vitest"; -import { User } from "../../src/Domain/Models/User"; - -describe("User", () => { - it("should create a user", function () { - const user = User.create(crypto.randomUUID()); - - expect(user).to.be.instanceOf(User); - }); -}); diff --git a/tests/Domain/vehicle.test.ts b/tests/Domain/vehicle.test.ts deleted file mode 100644 index 564209f..0000000 --- a/tests/Domain/vehicle.test.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { describe, expect, it } from "vitest"; -import { Vehicle } from "../../src/Domain/Models/Vehicle"; -import { generateFrenchPlateNumber } from "../Utils/generateFrenchPlateNumber"; - -describe("User", () => { - it("should create a vehicle", function () { - const vehicle = Vehicle.create(generateFrenchPlateNumber()); - - expect(vehicle).to.be.instanceOf(Vehicle); - }); -}); diff --git a/tests/Infra/Secondary/prismaFleetRepository.test.ts b/tests/Infra/Secondary/prismaFleetRepository.test.ts index a1559f6..7affe18 100644 --- a/tests/Infra/Secondary/prismaFleetRepository.test.ts +++ b/tests/Infra/Secondary/prismaFleetRepository.test.ts @@ -1,20 +1,9 @@ import { beforeEach, describe, expect, it } from "vitest"; import { Fleet } from "../../../src/Domain/Models/Fleet"; -import { PrismaFleetRepository } from "../../../src/Secondary/Repositories/PrismaFleetRepository"; +import { PrismaFleetRepository } from "../../../src/Infra/Secondary/Repositories/PrismaFleetRepository"; import { fleet, updatedFleet } from "../../Domain/fleetFixtures"; import { resetDB } from "./resetDB"; -function sortFleetVehicles(fleet: Fleet | undefined): Fleet { - if (!fleet) { - throw new Error("Fleet is undefined"); - } - - const sortedVehicles = [...fleet.vehicles].sort((a, b) => - a.plateNumber.localeCompare(b.plateNumber), - ); - return Fleet.create(fleet.id, fleet.userId, sortedVehicles); -} - describe("PrismaFleetRepository", () => { beforeEach(async () => { await resetDB(); @@ -44,4 +33,15 @@ describe("PrismaFleetRepository", () => { sortFleetVehicles(updatedFleet()), ); }); + + function sortFleetVehicles(fleet: Fleet | undefined): Fleet { + if (!fleet) { + throw new Error("Fleet is undefined"); + } + + const sortedVehicles = [...fleet.vehicles].sort((a, b) => + a.plateNumber.localeCompare(b.plateNumber), + ); + return Fleet.create(fleet.id, fleet.userId, sortedVehicles); + } }); diff --git a/tests/Infra/Secondary/resetDB.ts b/tests/Infra/Secondary/resetDB.ts index 763d1ce..f3d3a26 100644 --- a/tests/Infra/Secondary/resetDB.ts +++ b/tests/Infra/Secondary/resetDB.ts @@ -1,4 +1,4 @@ -import { prisma } from "../../../src/Secondary/client"; +import { prisma } from "../../../src/Infra/Secondary/client"; export async function resetDB() { console.log("Resetting database..."); diff --git a/tsconfig.json b/tsconfig.json index 1a1c0e9..7fd74a6 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,21 +1,74 @@ { "compilerOptions": { - "target": "ES2020", - "module": "ESNext", - "moduleResolution": "bundler", - "outDir": "build", - "rootDir": ".", - "baseUrl": ".", - "paths": { - "@/*": ["./*"] - }, - "allowImportingTsExtensions": false, - "resolveJsonModule": true, - "esModuleInterop": true, - "skipLibCheck": true, - "strict": true, - "forceConsistentCasingInFileNames": true + /* Visit https://aka.ms/tsconfig.json to read more about this file */ + + /* Basic Options */ + // "incremental": true, /* Enable incremental compilation */ + "target": "ES2022", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019', 'ES2020', 'ES2021', or 'ESNEXT'. */ + "module": "commonjs", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', 'es2020', or 'ESNext'. */ + "lib": ["ES2022"], /* Specify library files to be included in the compilation. */ + // "allowJs": true, /* Allow javascript files to be compiled. */ + // "checkJs": true, /* Report errors in .js files. */ + // "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', 'react', 'react-jsx' or 'react-jsxdev'. */ + // "declaration": true, /* Generates corresponding '.d.ts' file. */ + // "declarationMap": true, /* Generates a sourcemap for each corresponding '.d.ts' file. */ + // "sourceMap": true, /* Generates corresponding '.map' file. */ + // "outFile": "./", /* Concatenate and emit output to single file. */ + "outDir": "./dist", /* Redirect output structure to the directory. */ + // "rootDir": "./", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */ + // "composite": true, /* Enable project compilation */ + // "tsBuildInfoFile": "./", /* Specify file to store incremental compilation information */ + // "removeComments": true, /* Do not emit comments to output. */ + // "noEmit": true, /* Do not emit outputs. */ + // "importHelpers": true, /* Import emit helpers from 'tslib'. */ + // "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */ + // "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */ + + /* Strict Type-Checking Options */ + "strict": true, /* Enable all strict type-checking options. */ + // "noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */ + // "strictNullChecks": true, /* Enable strict null checks. */ + // "strictFunctionTypes": true, /* Enable strict checking of function types. */ + // "strictBindCallApply": true, /* Enable strict 'bind', 'call', and 'apply' methods on functions. */ + // "strictPropertyInitialization": true, /* Enable strict checking of property initialization in classes. */ + // "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */ + // "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */ + + /* Additional Checks */ + // "noUnusedLocals": true, /* Report errors on unused locals. */ + // "noUnusedParameters": true, /* Report errors on unused parameters. */ + // "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */ + // "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */ + // "noUncheckedIndexedAccess": true, /* Include 'undefined' in index signature results */ + // "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an 'override' modifier. */ + // "noPropertyAccessFromIndexSignature": true, /* Require undeclared properties from index signatures to use element accesses. */ + + /* Module Resolution Options */ + // "moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */ + // "baseUrl": ".", /* Base directory to resolve non-absolute module names. */ + // "paths": {}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */ + // "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */ + // "typeRoots": [], /* List of folders to include type definitions from. */ + // "types": [], /* Type declaration files to be included in compilation. */ + // "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */ + "esModuleInterop": true, /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */ + // "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */ + // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ + + /* Source Map Options */ + // "sourceRoot": "", /* Specify the location where debugger should locate TypeScript files instead of source locations. */ + // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ + // "inlineSourceMap": true, /* Emit a single file with source maps instead of having a separate file. */ + // "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */ + + /* Experimental Options */ + // "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */ + // "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */ + + /* Advanced Options */ + "skipLibCheck": true, /* Skip type checking of declaration files. */ + "forceConsistentCasingInFileNames": true /* Disallow inconsistently-cased references to the same file. */ }, - "include": ["src/**/*.ts"], - "exclude": ["node_modules", "build", "tests"] + "include": ["src/**/*.ts", "features/steps/**/*.ts"], + "exclude": ["node_modules", "dist", "tests"] } diff --git a/vitest.config.ts b/vitest.config.ts index 2bbde57..f612c07 100644 --- a/vitest.config.ts +++ b/vitest.config.ts @@ -2,6 +2,6 @@ import { defineConfig } from "vitest/config"; export default defineConfig({ test: { - exclude: ["build/**", "node_modules/**"], + exclude: ["dist/**", "node_modules/**"], }, });