Skip to content
Open

Tests #113

Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
74ec818
Initial unit tests and proxmox integration tests
anishapant21 Dec 10, 2025
5a3f32f
Tests for mongodb backend, utils fixes
anishapant21 Dec 11, 2025
e544b35
Fix cn wildcard tests
anishapant21 Dec 11, 2025
aaa6fcc
Integration tests for sql
anishapant21 Dec 11, 2025
d652199
Tests for require auth for search
anishapant21 Dec 11, 2025
e2f8218
TLS and ciphers test
anishapant21 Dec 12, 2025
cd1825a
Integration tests for sssd
anishapant21 Dec 15, 2025
3b9fd8b
Fix CI: use npm install instead of npm ci for workspace compatibility
anishapant21 Dec 15, 2025
1b2b1b3
Add SSSD integration tests with separate CI job
anishapant21 Dec 15, 2025
64dd5f8
Fix tests
anishapant21 Dec 15, 2025
c1d1614
Remove unwanted console logs and add delay
anishapant21 Dec 15, 2025
73d6684
Fix CI test failures: clean console logs and fix requireAuthForSearch…
anishapant21 Dec 15, 2025
af18212
Fix CI test failures: clean up console logs and split requireAuthForS…
anishapant21 Dec 15, 2025
527127b
Fix CI test failures: use npm ci and clean up console logs
anishapant21 Dec 15, 2025
9ea6245
Commit package lock
anishapant21 Dec 15, 2025
b137fda
Temp skip failing test
anishapant21 Dec 15, 2025
2c13efa
Resolve merge conflict and fix failing test
anishapant21 Dec 17, 2025
1f5685e
Fix ldap utils tests
anishapant21 Dec 17, 2025
62557c1
Fix gid number
anishapant21 Dec 17, 2025
9555035
Remove unused params
anishapant21 Dec 17, 2025
fca3bb3
Tests for ldaputils
anishapant21 Dec 17, 2025
52ac111
E2E tests
anishapant21 Dec 18, 2025
3892bbc
Add missing test file
anishapant21 Dec 18, 2025
169ed7f
Centeralize test data
anishapant21 Dec 18, 2025
0c54b84
Fix failing tests
anishapant21 Dec 18, 2025
f423398
Remove manual setting of binddn
anishapant21 Dec 18, 2025
440ae0e
Return promise from bind handler to enable async authentication
anishapant21 Dec 18, 2025
9bb00c2
Remove duplicate provider cleanup in MongoDB auth tests
anishapant21 Dec 18, 2025
ca62429
Folder restructure
anishapant21 Dec 18, 2025
c893751
Update readme
anishapant21 Dec 18, 2025
6e24e37
Add tests for sqlite and postgres db
anishapant21 Dec 22, 2025
cfb3e83
Build core and server before tests
anishapant21 Dec 22, 2025
751620c
Fix CI database test failures
anishapant21 Dec 22, 2025
a111a37
Remove reduandant tests and require uid and gid number
anishapant21 Dec 22, 2025
15d1030
Fix tests
anishapant21 Dec 22, 2025
9fa4b5b
Minor updates
anishapant21 Dec 22, 2025
f69ba53
Remove old tests
anishapant21 Dec 22, 2025
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
122 changes: 116 additions & 6 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,17 @@ name: CI Test

on:
push:
branches: [ main, develop ]
branches: [ main, dev, tests ]
pull_request:
branches: [ main ]

env:
NODE_VERSION: '18'

jobs:
test:
name: Test Build Process
# Stage 1: Fast unit tests (fail fast)
unit-tests:
name: Unit Tests
runs-on: ubuntu-latest
steps:
- name: Checkout repository
Expand All @@ -29,11 +30,120 @@ jobs:
- name: Build core package
run: npm run build:core

- name: Test core package
- name: Test core package (unit tests)
run: npm run test:core

- name: Build server package
run: npm run build:server

- name: Test server package
run: npm run test:server
- name: Generate test certificates
run: |
mkdir -p server/cert
openssl req -x509 -newkey rsa:2048 -nodes -sha256 \
-subj "/CN=localhost" \
-keyout server/cert/server.key \
-out server/cert/server.crt \
-days 1

- name: Test server package (unit/integration tests)
run: npm run test:server

# Stage 2: Database integration tests (parallel, after unit tests pass)
mysql-integration:
name: MySQL Integration Tests
runs-on: ubuntu-latest
needs: unit-tests
steps:
- name: Checkout repository
uses: actions/checkout@v4

- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: ${{ env.NODE_VERSION }}
cache: 'npm'

- name: Install dependencies
run: npm ci

- name: Build core package
run: npm run build:core

- name: Build server package
run: npm run build:server

- name: Run MySQL integration tests
run: npm run test:db:mysql -w server

postgres-integration:
name: PostgreSQL Integration Tests
runs-on: ubuntu-latest
needs: unit-tests
steps:
- name: Checkout repository
uses: actions/checkout@v4

- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: ${{ env.NODE_VERSION }}
cache: 'npm'

- name: Install dependencies
run: npm ci

- name: Build core package
run: npm run build:core

- name: Build server package
run: npm run build:server

- name: Run PostgreSQL integration tests
run: npm run test:db:postgres -w server

mongodb-integration:
name: MongoDB Integration Tests
runs-on: ubuntu-latest
needs: unit-tests
steps:
- name: Checkout repository
uses: actions/checkout@v4

- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: ${{ env.NODE_VERSION }}
cache: 'npm'

- name: Install dependencies
run: npm ci

- name: Build core package
run: npm run build:core

- name: Build server package
run: npm run build:server

- name: Run MongoDB integration tests
run: npm run test:db:mongodb -w server

# Stage 3: End-to-End tests (only after all unit and integration tests pass)
e2e:
name: End-to-End Tests
runs-on: ubuntu-latest
needs: [unit-tests, mysql-integration, postgres-integration, mongodb-integration]
steps:
- name: Checkout repository
uses: actions/checkout@v4

- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: ${{ env.NODE_VERSION }}
cache: 'npm'

- name: Install dependencies
run: npm ci

- name: Run E2E tests
run: npm run test:e2e -w server
42 changes: 34 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -439,7 +439,36 @@ AUTH_BACKENDS=my-auth

## 🧪 Testing

### LDAP Queries
The LDAP Gateway has a comprehensive test suite organized following the **Testing Pyramid** pattern:

### Quick Start

```bash
# Run all tests
npm test

# Run specific test types
npm run test:unit # Fast, isolated unit tests
npm run test:integration # Integration tests with real backends
npm run test:e2e # End-to-end tests with SSSD client

# Run database integration tests with Docker
npm run test:db:mysql # MySQL integration tests
npm run test:db:postgres # PostgreSQL integration tests
npm run test:db:mongodb # MongoDB integration tests
```

### Test Organization

- **Unit Tests**: Fast, isolated tests with mocked dependencies
- **Integration Tests**: Tests with real backends (SQLite, MySQL, PostgreSQL, MongoDB, Proxmox)
- **E2E Tests**: Full system tests with real LDAP clients (SSSD, SSH)

See [server/test/README.md](server/test/README.md) for detailed documentation.

### Manual Testing

#### LDAP Queries
```bash
# Search for users
ldapsearch -x -H ldaps://localhost:636 -b "dc=company,dc=com" "(uid=john)"
Expand All @@ -451,16 +480,13 @@ ldapsearch -x -H ldaps://localhost:636 -b "dc=company,dc=com" "(objectClass=posi
ldapsearch -x -H ldaps://localhost:636 -b "dc=company,dc=com" "(objectClass=posixGroup)"
```

### SSH Authentication
#### SSH Authentication
```bash
# Test SSH authentication through SSSD
ssh john@ldap-client-host

# Test with specific port
ssh john@localhost -p 2222
# Test SSH authentication through E2E SSSD client
ssh testuser@localhost -p 2222
```

### Health Check
#### Health Check
```bash
# Check service status
systemctl status ldap-gateway
Expand Down
67 changes: 67 additions & 0 deletions npm/jest.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
/**
* Jest Configuration for @ldap-gateway/core
*
* Tests the core npm package in isolation
* - Utilities (ldapUtils, filterUtils, errorUtils)
* - Base interfaces (AuthProvider, DirectoryProvider)
* - LdapEngine lifecycle
*/
module.exports = {
// Use Node.js test environment
testEnvironment: 'node',

// Test file patterns
testMatch: [
'**/test/**/*.test.js',
'**/test/**/*.spec.js'
],

// Coverage collection
collectCoverageFrom: [
'src/**/*.js',
'!src/**/*.test.js',
'!**/node_modules/**'
],

// Coverage thresholds (Phase 2: utilities + interfaces complete)
// LdapEngine partially tested (lifecycle only, full integration in Phase 3)
coverageThreshold: {
// High coverage required for utilities (Phase 1)
'./src/utils/**/*.js': {
branches: 95,
functions: 95,
lines: 95,
statements: 95
},
// High coverage for interfaces (Phase 2)
'./src/AuthProvider.js': {
branches: 80,
functions: 80,
lines: 80,
statements: 80
},
'./src/DirectoryProvider.js': {
branches: 80,
functions: 80,
lines: 80,
statements: 80
}
},

// Clear mocks between tests
clearMocks: true,
resetMocks: true,
restoreMocks: true,

// Timeout for tests (5s default, some integration tests may need more)
testTimeout: 5000,

// Verbose output
verbose: true,

// Module paths
roots: ['<rootDir>/src', '<rootDir>/test'],

// Transform - no need for babel, we use plain JS
transform: {}
};
15 changes: 10 additions & 5 deletions npm/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,9 @@
],
"scripts": {
"build": "node build.js",
"test": "echo \"No tests yet\" && exit 0",
"test": "jest",
"test:watch": "jest --watch",
"test:coverage": "jest --coverage",
"prepublishOnly": "npm run build"
},
"keywords": [
Expand All @@ -26,11 +28,14 @@
"node": ">=18.0.0"
},
"peerDependencies": {
"ldapjs": "git+https://github.com/mieweb/node-ldapjs.git",
"@ldapjs/controls": "git+https://github.com/mieweb/controls.git"
"@ldapjs/controls": "git+https://github.com/mieweb/controls.git",
"ldapjs": "git+https://github.com/mieweb/node-ldapjs.git"
},
"dependencies": {
"winston": "^3.17.0"
},
"devDependencies": {}
}
"devDependencies": {
"@types/jest": "30.0.0",
"jest": "30.2.0"
}
}
41 changes: 31 additions & 10 deletions npm/src/LdapEngine.js
Original file line number Diff line number Diff line change
Expand Up @@ -183,20 +183,20 @@ class LdapEngine extends EventEmitter {
this.emit('bindRequest', { username: 'anonymous', anonymous: true });
this.emit('bindSuccess', { username: 'anonymous', anonymous: true });
res.end();
return next();
});

// Authenticated bind - catch all DNs under our base
this.server.bind(this.config.baseDn, async (req, res, next) => {
this.server.bind(this.config.baseDn, (req, res, next) => {
const { username, password } = this._extractCredentials(req);
this.logger.debug("Authenticated bind request", { username, dn: req.dn.toString() });

try {
this.emit('bindRequest', { username, anonymous: false });

// Authenticate against all auth providers - all must return true
const authResults = await Promise.all(
this.authProviders.map(provider => provider.authenticate(username, password, req))
);
this.emit('bindRequest', { username, anonymous: false });

// Authenticate against all auth providers - all must return true
return Promise.all(
this.authProviders.map(provider => provider.authenticate(username, password, req))
).then(authResults => {
const isAuthenticated = authResults.every(result => result === true);

if (!isAuthenticated) {
Expand All @@ -207,13 +207,14 @@ class LdapEngine extends EventEmitter {

this.emit('bindSuccess', { username, anonymous: false });
res.end();
} catch (error) {
return next();
}).catch(error => {
this.logger.error("Bind error", { error, username });
const { normalizeAuthError } = require('./utils/errorUtils');
const normalizedError = normalizeAuthError(error);
this.emit('bindError', { username, error: normalizedError });
return next(normalizedError);
}
});
});
}

Expand Down Expand Up @@ -360,11 +361,26 @@ class LdapEngine extends EventEmitter {
if (isMixedSearchRequest(filterStr)) {
this.logger.debug(`Mixed search request with filter: ${filterStr}`);

// Parse cn value from filter for filtering
const cnMatch = filterStr.match(/cn=([^)&|]+)/i);
const cnFilter = cnMatch ? cnMatch[1].trim() : null;
const isWildcard = cnFilter === '*';

// Return users first
const users = await this.directoryProvider.getAllUsers();
this.logger.debug(`Found ${users.length} users for mixed search`);

for (const user of users) {
// Filter by cn if specified (cn is the user's common name)
if (cnFilter && !isWildcard) {
const userCn = user.firstname && user.lastname
? `${user.firstname} ${user.lastname}`
: user.username;
if (userCn.toLowerCase() !== cnFilter.toLowerCase()) {
continue;
}
}

const entry = createLdapEntry(user, this.config.baseDn);
this.emit('entryFound', { type: 'user', entry: entry.dn });
res.send(entry);
Expand All @@ -376,6 +392,11 @@ class LdapEngine extends EventEmitter {
this.logger.debug(`Found ${groups.length} groups for mixed search`);

for (const group of groups) {
// Filter by cn if specified (cn is the group name)
if (cnFilter && !isWildcard && group.name.toLowerCase() !== cnFilter.toLowerCase()) {
continue;
}

const entry = createLdapGroupEntry(group, this.config.baseDn);
this.emit('entryFound', { type: 'group', entry: entry.dn });
res.send(entry);
Expand Down
Loading