Files
drone-detector/server/tests/middleware/multi-tenant-auth.test.js
2025-09-15 06:38:23 +02:00

192 lines
5.8 KiB
JavaScript

const { describe, it, beforeEach, afterEach, before, after } = require('mocha');
const { expect } = require('chai');
const sinon = require('sinon');
const MultiTenantAuth = require('../../middleware/multi-tenant-auth');
const { setupTestEnvironment, teardownTestEnvironment, cleanDatabase, mockRequest, mockResponse, mockNext, createTestUser, createTestTenant, generateTestToken } = require('../setup');
describe('Multi-Tenant Authentication Middleware', () => {
let models, sequelize, multiAuth;
before(async () => {
({ models, sequelize } = await setupTestEnvironment());
multiAuth = new MultiTenantAuth();
});
after(async () => {
await teardownTestEnvironment();
});
beforeEach(async () => {
await cleanDatabase();
});
describe('determineTenant', () => {
it('should extract tenant from tenantId in JWT token', async () => {
const tenant = await createTestTenant({ slug: 'test-tenant' });
const user = await createTestUser({ tenant_id: tenant.id });
const req = mockRequest({
user: { tenantId: tenant.slug }
});
const result = await multiAuth.determineTenant(req);
expect(result).to.equal(tenant.slug);
});
it('should extract tenant from subdomain', async () => {
const req = mockRequest({
headers: { host: 'tenant1.example.com' }
});
const result = await multiAuth.determineTenant(req);
expect(result).to.equal('tenant1');
});
it('should extract tenant from complex subdomain', async () => {
const req = mockRequest({
headers: { host: 'uamils-ab.dev.uggla.uamils.com' }
});
const result = await multiAuth.determineTenant(req);
expect(result).to.equal('uamils-ab');
});
it('should extract tenant from domain path', async () => {
const req = mockRequest({
headers: { host: 'example.com' },
url: '/tenant2/api/devices'
});
const result = await multiAuth.determineTenant(req);
expect(result).to.equal('tenant2');
});
it('should prioritize JWT tenantId over subdomain', async () => {
const req = mockRequest({
user: { tenantId: 'jwt-tenant' },
headers: { host: 'subdomain-tenant.example.com' }
});
const result = await multiAuth.determineTenant(req);
expect(result).to.equal('jwt-tenant');
});
it('should return null for localhost without tenant info', async () => {
const req = mockRequest({
headers: { host: 'localhost:3000' }
});
const result = await multiAuth.determineTenant(req);
expect(result).to.be.null;
});
it('should handle x-forwarded-host header', async () => {
const req = mockRequest({
headers: {
host: 'localhost:3000',
'x-forwarded-host': 'tenant3.example.com'
}
});
const result = await multiAuth.determineTenant(req);
expect(result).to.equal('tenant3');
});
});
describe('middleware function', () => {
it('should pass through when tenant is determined', async () => {
const tenant = await createTestTenant({ slug: 'valid-tenant' });
const req = mockRequest({
user: { tenantId: tenant.slug }
});
const res = mockResponse();
const next = mockNext();
await multiAuth.authenticate(req, res, next);
expect(req.tenant).to.equal(tenant.slug);
expect(next.errors).to.have.length(0);
});
it('should reject when tenant cannot be determined', async () => {
const req = mockRequest({
headers: { host: 'localhost:3000' }
});
const res = mockResponse();
const next = mockNext();
await multiAuth.authenticate(req, res, next);
expect(res.statusCode).to.equal(400);
expect(res.data).to.deep.equal({
success: false,
message: 'Unable to determine tenant'
});
});
it('should reject when tenant does not exist in database', async () => {
const req = mockRequest({
user: { tenantId: 'nonexistent-tenant' }
});
const res = mockResponse();
const next = mockNext();
await multiAuth.authenticate(req, res, next);
expect(res.statusCode).to.equal(404);
expect(res.data).to.deep.equal({
success: false,
message: 'Tenant not found'
});
});
it('should reject when tenant is inactive', async () => {
const tenant = await createTestTenant({
slug: 'inactive-tenant',
is_active: false
});
const req = mockRequest({
user: { tenantId: tenant.slug }
});
const res = mockResponse();
const next = mockNext();
await multiAuth.authenticate(req, res, next);
expect(res.statusCode).to.equal(403);
expect(res.data).to.deep.equal({
success: false,
message: 'Tenant is not active'
});
});
});
describe('validateTenantAccess', () => {
it('should validate user belongs to tenant', async () => {
const tenant = await createTestTenant({ slug: 'user-tenant' });
const user = await createTestUser({ tenant_id: tenant.id });
const isValid = await multiAuth.validateTenantAccess(user.id, tenant.slug);
expect(isValid).to.be.true;
});
it('should reject user from different tenant', async () => {
const tenant1 = await createTestTenant({ slug: 'tenant1' });
const tenant2 = await createTestTenant({ slug: 'tenant2' });
const user = await createTestUser({ tenant_id: tenant1.id });
const isValid = await multiAuth.validateTenantAccess(user.id, tenant2.slug);
expect(isValid).to.be.false;
});
it('should reject nonexistent user', async () => {
const tenant = await createTestTenant({ slug: 'valid-tenant' });
const isValid = await multiAuth.validateTenantAccess(99999, tenant.slug);
expect(isValid).to.be.false;
});
});
});