Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 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
6 changes: 4 additions & 2 deletions .github/copilot-instructions.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,16 @@

## Canonical commands

- Dev: `npm start`
- Dev: `npm start` (alias: `npm run dev`)
- Debug: `npm run debug`
- Tests: `npm test` (watch: `npm run test:watch`)
- Tests: `npm test` / `npm run test:unit` (one-shot) or `npm run test:watch` (watch)
- Coverage: `npm run test:coverage`
- Lint: `npm run lint`
- Lint fix: `npm run lint:fix`
- Format: `npm run format`
- Seed: `npm run seed:dev`
- Commit: `npm run commit`
- Release (CI): `npm run release:auto`

## Available prompts

Expand Down
30 changes: 18 additions & 12 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,18 +12,24 @@ The `.claude/` folder contains embedded settings, skills, and agents that are av

## Canonical commands

| Command | Script | Description |
| ------------- | ------------------------ | ---------------------------------------------- |
| **Dev** | `npm start` | Start dev server at `http://localhost:3000/` |
| **Debug** | `npm run debug` | Start with nodemon and inspector |
| **Test** | `npm test` | Run all tests |
| **Watch** | `npm run test:watch` | Run tests in watch mode |
| **Coverage** | `npm run test:coverage` | Generate test coverage |
| **Lint** | `npm run lint` | Check code quality |
| **Lint fix** | `npm run lint:fix` | Auto-fix linting issues |
| **Seed** | `npm run seed:dev` | Seed development database |
| **Commit** | `npm run commit` | Commit with commitizen |
| **Docker** | `docker-compose up` | Start with docker-compose |
| Command | Script | Description |
| ---------------- | ------------------------ | ---------------------------------------------- |
| **Dev** | `npm start` | Start dev server at `http://localhost:3000/` |
| **Dev (alias)** | `npm run dev` | Alias for `npm start` |
| **Debug** | `npm run debug` | Start with nodemon and inspector |
| **Prod** | `npm run prod` | Start in production mode |
| **Test** | `npm test` | Run all tests (one-shot) |
| **Unit test** | `npm run test:unit` | Run unit tests once (alias of `npm test`) |
| **Watch** | `npm run test:watch` | Run tests in watch mode |
| **Coverage** | `npm run test:coverage` | Generate test coverage |
| **Lint** | `npm run lint` | Check code quality |
| **Lint fix** | `npm run lint:fix` | Auto-fix linting issues |
| **Format** | `npm run format` | Format with Prettier |
| **Seed** | `npm run seed:dev` | Seed development database |
| **Commit** | `npm run commit` | Commit with commitizen |
| **Release** | `npm run release` | Manual release (standard-version) |
| **Release (CI)** | `npm run release:auto` | Semantic release for CI |
| **Docker** | `docker-compose up` | Start with docker-compose |

## Preflight

Expand Down
6 changes: 4 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ npm install
### Development

```bash
npm start
npm start # or: npm run dev
```

Runs the server at `http://localhost:3000/`. For auto-reload during development, use `npm run debug` (nodemon).
Expand All @@ -80,7 +80,8 @@ npm run prod
### Testing

```bash
npm test # Run all tests
npm test # Run all tests (one-shot)
npm run test:unit # Run unit tests once (alias of npm test)
npm run test:watch # Run tests in watch mode
npm run test:coverage # Generate coverage report
```
Expand All @@ -92,6 +93,7 @@ Tests are organized per module in `modules/*/tests/`
```bash
npm run lint # Check code quality (read-only)
npm run lint:fix # Auto-fix code quality issues
npm run format # Format code with Prettier
```

### Database Seeding
Expand Down
14 changes: 10 additions & 4 deletions modules/auth/controllers/auth.controller.js
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,7 @@ const oauthCall = (req, res, next) => {
* @param {Object} providerUserProfile
* @param {Function} done - done
*/
Comment thread
PierreBrisorgueil marked this conversation as resolved.
const checkOAuthUserProfile = async (profil, key, provider, res) => {
const checkOAuthUserProfile = async (profil, key, provider) => {
// check if user exist
try {
const query = {};
Expand All @@ -137,10 +137,11 @@ const checkOAuthUserProfile = async (profil, key, provider, res) => {
const result = model.getResultFromZod(user, UsersSchema.User);
// check error
const error = model.checkError(result);
if (error) return responses.error(res, 422, 'Schema validation error', error)(result.error);
if (error) throw new AppError('Schema validation error', { code: 'VALIDATION_ERROR', details: { message: error } });
// else return req.body with the data after Zod validation
return await UserService.create(result.value);
} catch (err) {
if (err instanceof AppError) throw err;
throw new AppError('oAuth', { code: 'CONTROLLER_ERROR', details: err.details || err });
}
};
Expand All @@ -163,7 +164,7 @@ const oauthCallback = async (req, res, next) => {
providerData: {},
};
user.providerData[req.body.key] = req.body.value;
user = await checkOAuthUserProfile(user, req.body.key, strategy, res);
user = await checkOAuthUserProfile(user, req.body.key, strategy);
const token = jwt.sign({ userId: user.id }, config.jwt.secret, {
expiresIn: config.jwt.expiresIn,
});
Expand All @@ -177,7 +178,12 @@ const oauthCallback = async (req, res, next) => {
message: 'oAuth Ok',
});
} catch (err) {
return responses.error(res, 422, 'Unprocessable Entity', errors.getMessage(err.details || err))(err);
return responses.error(
res,
422,
err instanceof AppError && err.code === 'VALIDATION_ERROR' ? errors.getMessage(err) : 'Unprocessable Entity',
errors.getMessage(err.details || err),
)(err);
Comment thread
PierreBrisorgueil marked this conversation as resolved.
}
}
// classic web oAuth
Expand Down
26 changes: 13 additions & 13 deletions modules/auth/tests/auth.integration.tests.js
Original file line number Diff line number Diff line change
Expand Up @@ -486,28 +486,30 @@ describe('Auth integration tests:', () => {
avatar: '',
providerData: { id: 'google-fake-id-999' },
};
const mockRes = { status() { return this; }, json() {}, cookie() { return this; } };
const result = await AuthController.checkOAuthUserProfile(profil, 'id', 'google', mockRes);
const result = await AuthController.checkOAuthUserProfile(profil, 'id', 'google');
expect(result).toBeDefined();
expect(result.id).toBeDefined();
expect(result.email).toBe(profil.email);
oauthUsers.push(result);
});

test('should return 422 when checkOAuthUserProfile receives an invalid profile', async () => {
test('should throw validation AppError when checkOAuthUserProfile receives an invalid profile', async () => {
const invalidProfil = {
firstName: '', // invalid — fails min(1)
lastName: 'Test',
email: 'invalid-oauth@test.com',
avatar: '',
providerData: { id: 'google-invalid-999' },
};
const errors = [];
const mockRes = { status() { return this; }, json(body) { errors.push(body); }, cookie() { return this; } };
const result = await AuthController.checkOAuthUserProfile(invalidProfil, 'id', 'google', mockRes);
expect(result).toBeDefined();
expect(result.type).toBe('error');
expect(errors[0]?.message).toBe('Schema validation error');
await expect(
AuthController.checkOAuthUserProfile(invalidProfil, 'id', 'google'),
).rejects.toMatchObject({
message: 'Schema validation error',
code: 'VALIDATION_ERROR',
details: {
message: expect.any(String),
},
});
});

test('should throw AppError when create fails inside checkOAuthUserProfile', async () => {
Expand All @@ -518,10 +520,9 @@ describe('Auth integration tests:', () => {
avatar: '',
providerData: { id: 'google-err-000' },
};
const mockRes = { status() { return this; }, json() {}, cookie() { return this; } };
const createSpy = jest.spyOn(UserService, 'create').mockRejectedValueOnce(new Error('DB error'));
await expect(
AuthController.checkOAuthUserProfile(profil, 'id', 'google', mockRes),
AuthController.checkOAuthUserProfile(profil, 'id', 'google'),
).rejects.toThrow('oAuth');
createSpy.mockRestore();
});
Expand Down Expand Up @@ -590,9 +591,8 @@ describe('Auth integration tests:', () => {
avatar: '',
providerData: { id: 'google-find-id-777' },
};
const mockRes = { status() { return this; }, json() {}, cookie() { return this; } };
// Second call — should find the existing user (search.length === 1 branch)
const found = await AuthController.checkOAuthUserProfile(profil, 'id', 'google', mockRes);
const found = await AuthController.checkOAuthUserProfile(profil, 'id', 'google');
expect(found).toBeDefined();

// cleanup
Expand Down
3 changes: 3 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,11 @@
},
"scripts": {
"start": "node server.js",
"dev": "npm run start",
"debug": "nodemon --inspect server.js",
"prod": "cross-env NODE_ENV=production node server.js --name=node",
"test": "cross-env NODE_ENV=test NODE_OPTIONS=--experimental-vm-modules jest --runInBand",
"test:unit": "cross-env NODE_ENV=test NODE_OPTIONS=--experimental-vm-modules jest --runInBand",
"test:watch": "cross-env NODE_ENV=test NODE_OPTIONS=--experimental-vm-modules jest --watch --runInBand",
"test:coverage": "cross-env NODE_ENV=test NODE_OPTIONS=--experimental-vm-modules jest --coverage --runInBand",
"lint": "eslint ./modules ./lib ./config ./scripts",
Expand All @@ -38,6 +40,7 @@
"seed:mongodrop": "node scripts/seed.js drop",
"generate:sllCerts": "scripts/generate-ssl-certs.sh",
"lint:fix": "eslint --fix ./modules ./lib ./config ./scripts",
"format": "prettier --write .",
"snyk-protect": "snyk protect",
"commit": "npx cz",
"release": "standard-version",
Expand Down