the beginning of the idiots

This commit is contained in:
2025-10-24 14:51:13 -05:00
parent 0b377030c6
commit cb06217ef7
123 changed files with 10279 additions and 0 deletions

13
qwen/nodejs/.dockerignore Normal file
View File

@@ -0,0 +1,13 @@
node_modules
npm-debug.log
.git
.gitignore
README.md
.env
.nyc_output
coverage
.nyc_output
.coverage
.coverage/
.vscode
.DS_Store

9
qwen/nodejs/.env Normal file
View File

@@ -0,0 +1,9 @@
NODE_ENV=development
PORT=19000
DB_HOST=localhost
DB_PORT=5432
DB_NAME=moh_portal
DB_USER=postgres
DB_PASSWORD=postgres
JWT_SECRET=secret_key_for_jwt_tokens
SESSION_SECRET=secret_key_for_session

85
qwen/nodejs/AGENTS.md Normal file
View File

@@ -0,0 +1,85 @@
Do not perform any operations on the host other than git and docker / docker compose operations
Utilize docker containers for all work done in this repository.
Utilize a docker artifact related name prefix of <codingagent>-<language>-<function> to make it easy to manage all the docker artifacts.
Only expose the main app web interface over the network. All other ports should remain on a per stack docker network.
Here are the port assignments for the containers
gemini/go 12000
gemini/hack 13000
gemini/nodejs 14000
gemini/php 15000
gemini/python 16000
qwen/go 17000
qwen//hack 18000
qwen/nodejs 19000
qwen/php 20000
qwen/python 21000
copilot/go 22000
copilot/gemini/hack 23000
copilot/nodejs 24000
copilot/php 25000
copilot/python 26000
The purpose of this repository is to test three coding agents:
qwen
copilot
gemini
and five programming languages:
go
hack
nodejs
php
python
against the following programming test:
I have purchased the domain name MerchantsOfHope.org and its intened to be the consulting/contracting arm of TSYS Group.
It will need to handle:
- Multiple independent tennants (TSYS Group has dozens and dozens of lines of business, all fully isolated from each other)
- OIDC and social media login
It will need to handle all functionality of a recuriting platform:
- Job seekers browsing postions and posting resumes/going through the application process
- Job providrrs managing the lifecycle of positions and applications
This should be pretty simple and off the shelf, bog standard type workflows.
Presume USA law compliance only.
No need for anything other than English to be supported.
Accessibility is critical, we have a number of US Government contracts and they mandate accessibility.
Also we need to be compliant with PCI, GDPR, SOC, FedRamp etc.
Use the name of the directory you are in to determine the programming language to use.
Do not create any artifacts outside of the directory you are in now.
You may manage the contents of this directory as you see fit.
Please keep it well organized.
Follow Test Driven Development for all your work.
Create and maintain a docker-compose.yml file with your service dependenices
Ship this application as a docker container.
This will eventually be deployed into a k8s cluster , so make sure to take that into account.
Also follow all best common practices for security, QA, engineering, SRE/devops etc.
Make it happen.

25
qwen/nodejs/Dockerfile Normal file
View File

@@ -0,0 +1,25 @@
# Use Node.js 18 LTS as the base image
FROM node:18-alpine
# Set working directory in the container
WORKDIR /app
# Copy package.json and package-lock.json (if available)
COPY package*.json ./
# Install dependencies
RUN npm ci --only=production
# Copy the rest of the application code
COPY . .
# Create a non-root user and switch to it
RUN addgroup -g 1001 -S nodejs
RUN adduser -S nextjs -u 1001
USER nextjs
# Expose the port the app runs on
EXPOSE 19000
# Define the command to run the application
CMD ["npm", "start"]

View File

@@ -0,0 +1,78 @@
// controllers/authController.js
const authService = require('../services/authService');
const login = async (req, res) => {
try {
const { email, password } = req.body;
if (!email || !password) {
return res.status(400).json({ error: 'Email and password are required' });
}
const result = await authService.login(email, password);
if (result.error) {
return res.status(401).json({ error: result.error });
}
res.status(200).json({
message: 'Login successful',
user: result.user,
token: result.token
});
} catch (error) {
console.error('Login error:', error);
res.status(500).json({ error: 'Internal server error' });
}
};
const register = async (req, res) => {
try {
const { email, password, firstName, lastName, userType } = req.body;
if (!email || !password || !firstName || !lastName || !userType) {
return res.status(400).json({ error: 'All fields are required' });
}
const result = await authService.register(email, password, firstName, lastName, userType, req.tenantId);
if (result.error) {
return res.status(400).json({ error: result.error });
}
res.status(201).json({
message: 'Registration successful',
user: result.user
});
} catch (error) {
console.error('Registration error:', error);
res.status(500).json({ error: 'Internal server error' });
}
};
const logout = async (req, res) => {
try {
// In a real implementation, you might invalidate the JWT token
res.status(200).json({ message: 'Logout successful' });
} catch (error) {
console.error('Logout error:', error);
res.status(500).json({ error: 'Internal server error' });
}
};
const getCurrentUser = async (req, res) => {
try {
// This would use middleware to verify JWT and extract user info
res.status(200).json({ user: req.user });
} catch (error) {
console.error('Get current user error:', error);
res.status(500).json({ error: 'Internal server error' });
}
};
module.exports = {
login,
register,
logout,
getCurrentUser
};

View File

@@ -0,0 +1,154 @@
// controllers/tenantController.js
// Controller for tenant-related operations
// Mock tenant storage - this would be a database in production
const tenants = [
{
id: 'default',
name: 'Default Tenant',
subdomain: 'default',
settings: {
allowedDomains: ['localhost', 'merchants-of-hope.org'],
features: ['job-posting', 'resume-uploading', 'application-tracking']
},
createdAt: new Date(),
updatedAt: new Date()
}
];
const getTenant = async (req, res) => {
try {
const { tenantId } = req.params;
// Find the requested tenant
const tenant = tenants.find(t => t.id === tenantId || t.subdomain === tenantId);
if (!tenant) {
return res.status(404).json({ error: 'Tenant not found' });
}
res.status(200).json({
tenant: {
id: tenant.id,
name: tenant.name,
subdomain: tenant.subdomain,
settings: tenant.settings,
createdAt: tenant.createdAt,
updatedAt: tenant.updatedAt
}
});
} catch (error) {
console.error('Get tenant error:', error);
res.status(500).json({ error: 'Internal server error' });
}
};
const createTenant = async (req, res) => {
try {
const { name, subdomain, settings } = req.body;
// Validate required fields
if (!name || !subdomain) {
return res.status(400).json({ error: 'Name and subdomain are required' });
}
// Check if tenant already exists
const existingTenant = tenants.find(t => t.subdomain === subdomain || t.name === name);
if (existingTenant) {
return res.status(409).json({ error: 'Tenant with this name or subdomain already exists' });
}
// Create new tenant
const newTenant = {
id: require('uuid').v4(),
name,
subdomain,
settings: settings || {},
createdAt: new Date(),
updatedAt: new Date()
};
tenants.push(newTenant);
res.status(201).json({
message: 'Tenant created successfully',
tenant: {
id: newTenant.id,
name: newTenant.name,
subdomain: newTenant.subdomain,
settings: newTenant.settings
}
});
} catch (error) {
console.error('Create tenant error:', error);
res.status(500).json({ error: 'Internal server error' });
}
};
const updateTenant = async (req, res) => {
try {
const { tenantId } = req.params;
const { name, settings } = req.body;
// Find the tenant to update
const tenantIndex = tenants.findIndex(t => t.id === tenantId || t.subdomain === tenantId);
if (tenantIndex === -1) {
return res.status(404).json({ error: 'Tenant not found' });
}
// Update tenant properties
if (name) {
tenants[tenantIndex].name = name;
}
if (settings) {
tenants[tenantIndex].settings = { ...tenants[tenantIndex].settings, ...settings };
}
tenants[tenantIndex].updatedAt = new Date();
res.status(200).json({
message: 'Tenant updated successfully',
tenant: {
id: tenants[tenantIndex].id,
name: tenants[tenantIndex].name,
subdomain: tenants[tenantIndex].subdomain,
settings: tenants[tenantIndex].settings,
updatedAt: tenants[tenantIndex].updatedAt
}
});
} catch (error) {
console.error('Update tenant error:', error);
res.status(500).json({ error: 'Internal server error' });
}
};
const deleteTenant = async (req, res) => {
try {
const { tenantId } = req.params;
// Find the tenant to delete
const tenantIndex = tenants.findIndex(t => t.id === tenantId || t.subdomain === tenantId);
if (tenantIndex === -1) {
return res.status(404).json({ error: 'Tenant not found' });
}
// In a real implementation, you'd want to also delete all related data
// For now, we'll just remove the tenant from our mock storage
tenants.splice(tenantIndex, 1);
res.status(200).json({
message: 'Tenant deleted successfully'
});
} catch (error) {
console.error('Delete tenant error:', error);
res.status(500).json({ error: 'Internal server error' });
}
};
module.exports = {
getTenant,
createTenant,
updateTenant,
deleteTenant
};

View File

@@ -0,0 +1,79 @@
version: '3.8'
services:
# Main application
app:
build: .
container_name: qwen-nodejs-app
ports:
- "19000:19000"
environment:
- NODE_ENV=production
- PORT=19000
- DB_HOST=postgres
- DB_PORT=5432
- DB_NAME=${DB_NAME:-moh_portal}
- DB_USER=${DB_USER:-postgres}
- DB_PASSWORD=${DB_PASSWORD:-postgres}
- JWT_SECRET=${JWT_SECRET:-secret_key_for_jwt_tokens}
- SESSION_SECRET=${SESSION_SECRET:-secret_key_for_session}
depends_on:
- postgres
- redis
networks:
- moh-network
restart: unless-stopped
# PostgreSQL database
postgres:
image: postgres:15-alpine
container_name: qwen-nodejs-postgres
ports:
- "5432:5432"
environment:
- POSTGRES_DB=${DB_NAME:-moh_portal}
- POSTGRES_USER=${DB_USER:-postgres}
- POSTGRES_PASSWORD=${DB_PASSWORD:-postgres}
volumes:
- postgres_data:/var/lib/postgresql/data
- ./init.sql:/docker-entrypoint-initdb.d/init.sql
networks:
- moh-network
restart: unless-stopped
# Redis for session storage and caching
redis:
image: redis:7-alpine
container_name: qwen-nodejs-redis
ports:
- "6379:6379"
volumes:
- redis_data:/data
networks:
- moh-network
restart: unless-stopped
command: redis-server --appendonly yes
# Nginx as reverse proxy (optional, can be added later)
nginx:
image: nginx:alpine
container_name: qwen-nodejs-nginx
ports:
- "80:80"
- "443:443"
volumes:
- ./nginx.conf:/etc/nginx/nginx.conf
- ./ssl:/etc/nginx/ssl
depends_on:
- app
networks:
- moh-network
restart: unless-stopped
volumes:
postgres_data:
redis_data:
networks:
moh-network:
driver: bridge

127
qwen/nodejs/index.js Normal file
View File

@@ -0,0 +1,127 @@
require('dotenv').config();
const express = require('express');
const cors = require('cors');
const helmet = require('helmet');
const rateLimit = require('express-rate-limit');
const path = require('path');
const http = require('http');
// Initialize Express app
const app = express();
// Security middleware
app.use(helmet({
contentSecurityPolicy: {
directives: {
defaultSrc: ["'self'"],
styleSrc: ["'self'", "'unsafe-inline'"],
scriptSrc: ["'self'"],
imgSrc: ["'self'", "data:", "https:"],
},
},
crossOriginEmbedderPolicy: false, // Needed for some static assets
}));
app.use(cors({
origin: process.env.NODE_ENV === 'production'
? [process.env.FRONTEND_URL]
: ['http://localhost:3000', 'http://localhost:19000'],
credentials: true
}));
// Rate limiting
const limiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15 minutes
max: 100, // limit each IP to 100 requests per windowMs
message: 'Too many requests from this IP, please try again later.'
});
app.use(limiter);
// Body parsing middleware
app.use(express.json({ limit: '10mb' }));
app.use(express.urlencoded({ extended: true }));
// Static files
app.use(express.static(path.join(__dirname, 'public')));
// Tenant resolution and isolation middleware
const { resolveTenant, enforceTenantIsolation } = require('./middleware/tenant');
app.use(resolveTenant);
app.use(enforceTenantIsolation);
// Import and use routes
const authRoutes = require('./routes/auth');
const jobSeekerRoutes = require('./routes/jobSeeker');
const jobProviderRoutes = require('./routes/jobProvider');
const tenantRoutes = require('./routes/tenant');
app.use('/api/auth', authRoutes);
app.use('/api/job-seekers', jobSeekerRoutes);
app.use('/api/job-providers', jobProviderRoutes);
app.use('/api/tenants', tenantRoutes);
// Basic route
app.get('/', (req, res) => {
res.json({
message: 'Welcome to MerchantsOfHope.org - TSYS Group Recruiting Platform',
status: 'running',
timestamp: new Date().toISOString(),
tenantId: req.tenantId
});
});
// Health check endpoint
app.get('/health', (req, res) => {
res.status(200).json({
status: 'OK',
timestamp: new Date().toISOString(),
service: 'MOH Portal API',
tenantId: req.tenantId
});
});
// Error handling middleware
app.use((err, req, res, next) => {
console.error(err.stack);
res.status(500).json({
error: 'Something went wrong!',
message: process.env.NODE_ENV === 'development' ? err.message : 'Internal server error',
tenantId: req.tenantId
});
});
// 404 handler
app.use('*', (req, res) => {
res.status(404).json({
error: 'Route not found',
tenantId: req.tenantId
});
});
module.exports = app;
// Only start the server if this file is run directly (not imported for testing)
if (require.main === module) {
const server = http.createServer(app);
const PORT = process.env.PORT || 19000;
server.listen(PORT, () => {
console.log(`MerchantsOfHope.org server running on port ${PORT}`);
console.log(`Tenant identification enabled - using tenant: default or from request`);
});
// Graceful shutdown
process.on('SIGTERM', () => {
console.log('SIGTERM received, shutting down gracefully');
server.close(() => {
console.log('Process terminated');
});
});
process.on('SIGINT', () => {
console.log('SIGINT received, shutting down gracefully');
server.close(() => {
console.log('Process terminated');
});
});
}

View File

@@ -0,0 +1,16 @@
module.exports = {
testEnvironment: 'node',
collectCoverageFrom: [
'**/*.{js,jsx,ts,tsx}',
'!**/node_modules/**',
'!**/coverage/**',
'!**/dist/**',
'!**/build/**',
],
testMatch: [
'<rootDir>/tests/**/*.test.{js,jsx,ts,tsx}',
'<rootDir>/**/?(*.)+(spec|test).{js,jsx,ts,tsx}',
],
setupFilesAfterEnv: ['<rootDir>/tests/setup.js'],
testTimeout: 30000,
};

View File

@@ -0,0 +1,89 @@
// middleware/tenant.js
// Middleware to handle tenant-specific operations
// Mock tenant storage - in a real implementation this would be a database
const tenants = [
{
id: 'default',
name: 'Default Tenant',
subdomain: 'default',
settings: {
allowedDomains: ['localhost', 'merchants-of-hope.org'],
features: ['job-posting', 'resume-uploading', 'application-tracking']
}
}
];
// Tenant resolution middleware
const resolveTenant = async (req, res, next) => {
let tenantId = null;
// Method 1: From subdomain (e.g., tenant1.merchants-of-hope.org)
if (req.headers.host) {
const hostParts = req.headers.host.split('.');
if (hostParts.length >= 3 && hostParts[0] !== 'www') {
tenantId = hostParts[0];
}
}
// Method 2: From header (for development)
if (!tenantId && req.headers['x-tenant-id']) {
tenantId = req.headers['x-tenant-id'];
}
// Method 3: From URL path (e.g., /tenant/tenant1/api/...)
if (!tenantId && req.originalUrl.startsWith('/tenant/')) {
const pathParts = req.originalUrl.split('/');
if (pathParts.length > 2) {
tenantId = pathParts[2];
// Remove tenant from URL for further routing
req.originalUrl = req.originalUrl.replace(`/tenant/${tenantId}`, '');
req.url = req.url.replace(`/tenant/${tenantId}`, '');
}
}
// Default to 'default' tenant if none found
if (!tenantId) {
tenantId = 'default';
}
// Find the tenant in our mock storage
const tenant = tenants.find(t => t.id === tenantId || t.subdomain === tenantId);
if (!tenant && tenantId !== 'default') {
return res.status(404).json({
error: 'Tenant not found',
tenantId: tenantId
});
}
// Set tenant in request object for other middleware/routes to use
req.tenant = tenant || {
id: 'default',
name: 'Default Tenant',
subdomain: 'default',
settings: {}
};
req.tenantId = req.tenant.id;
next();
};
// Middleware to enforce tenant isolation
const enforceTenantIsolation = async (req, res, next) => {
// In a real implementation, this would:
// 1. Set up a database connection or context per tenant
// 2. Ensure queries are scoped to the current tenant
// 3. Apply tenant-specific security policies
// For now, we'll just log the tenant for debugging
console.log(`Request for tenant: ${req.tenantId}`);
next();
};
module.exports = {
resolveTenant,
enforceTenantIsolation
};

View File

@@ -0,0 +1,41 @@
// models/Tenant.js
// Tenant model definition
class Tenant {
constructor(id, name, subdomain, settings, createdAt, updatedAt) {
this.id = id;
this.name = name;
this.subdomain = subdomain;
this.settings = settings || {};
this.createdAt = createdAt || new Date();
this.updatedAt = updatedAt || new Date();
}
// Static method to create a new tenant
static create(tenantData) {
const id = tenantData.id || require('uuid').v4();
return new Tenant(
id,
tenantData.name,
tenantData.subdomain,
tenantData.settings
);
}
// Method to validate a tenant
validate() {
if (!this.name || !this.subdomain) {
throw new Error('Tenant name and subdomain are required');
}
// Validate subdomain format (alphanumeric and hyphens only)
const subdomainRegex = /^[a-zA-Z0-9][a-zA-Z0-9-]{0,61}[a-zA-Z0-9]$/;
if (!subdomainRegex.test(this.subdomain)) {
throw new Error('Invalid subdomain format');
}
return true;
}
}
module.exports = Tenant;

View File

@@ -0,0 +1,50 @@
// models/User.js
// User model definition
class User {
constructor(id, email, passwordHash, firstName, lastName, userType, tenantId, createdAt, updatedAt) {
this.id = id;
this.email = email;
this.passwordHash = passwordHash;
this.firstName = firstName;
this.lastName = lastName;
this.userType = userType; // 'job-seeker' or 'job-provider'
this.tenantId = tenantId;
this.createdAt = createdAt || new Date();
this.updatedAt = updatedAt || new Date();
}
// Static method to create a new user
static create(userData) {
const id = userData.id || require('uuid').v4();
return new User(
id,
userData.email,
userData.passwordHash,
userData.firstName,
userData.lastName,
userData.userType,
userData.tenantId
);
}
// Method to validate a user
validate() {
if (!this.email || !this.passwordHash || !this.firstName || !this.lastName || !this.userType || !this.tenantId) {
throw new Error('Missing required fields');
}
if (!['job-seeker', 'job-provider'].includes(this.userType)) {
throw new Error('User type must be either job-seeker or job-provider');
}
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
if (!emailRegex.test(this.email)) {
throw new Error('Invalid email format');
}
return true;
}
}
module.exports = User;

View File

@@ -0,0 +1,11 @@
// models/index.js
// This would typically connect to the database and export all models
// For now, we'll define a simple structure
const User = require('./User');
const Tenant = require('./Tenant');
module.exports = {
User,
Tenant
};

26
qwen/nodejs/nginx.conf Normal file
View File

@@ -0,0 +1,26 @@
events {
worker_connections 1024;
}
http {
upstream nodejs_backend {
server app:19000;
}
server {
listen 80;
server_name _;
location / {
proxy_pass http://nodejs_backend;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_cache_bypass $http_upgrade;
}
}
}

51
qwen/nodejs/package.json Normal file
View File

@@ -0,0 +1,51 @@
{
"name": "moh-portal",
"version": "1.0.0",
"description": "MerchantsOfHope.org recruiting platform for TSYS Group",
"main": "index.js",
"scripts": {
"start": "node index.js",
"dev": "nodemon index.js",
"test": "jest",
"test:watch": "jest --watch",
"lint": "eslint .",
"lint:fix": "eslint . --fix"
},
"keywords": [
"recruiting",
"job-platform",
"multi-tenant",
"oidc"
],
"author": "TSYS Group",
"license": "MIT",
"dependencies": {
"express": "^4.18.2",
"dotenv": "^16.3.1",
"cors": "^2.8.5",
"helmet": "^7.0.0",
"express-rate-limit": "^6.10.0",
"joi": "^17.9.2",
"bcryptjs": "^2.4.3",
"jsonwebtoken": "^9.0.2",
"pg": "^8.11.2",
"sequelize": "^6.32.1",
"passport": "^0.6.0",
"passport-jwt": "^4.0.1",
"passport-local": "^1.0.0",
"express-session": "^1.17.3",
"connect-session-sequelize": "^7.1.7",
"multer": "^1.4.5-lts.1",
"uuid": "^9.0.0",
"axios": "^1.5.0"
},
"devDependencies": {
"nodemon": "^3.0.1",
"jest": "^29.6.2",
"supertest": "^6.3.3",
"eslint": "^8.47.0",
"@babel/core": "^7.22.10",
"@babel/preset-env": "^7.22.10",
"babel-jest": "^29.6.2"
}
}

View File

@@ -0,0 +1,17 @@
const express = require('express');
const router = express.Router();
const { login, register, logout, getCurrentUser } = require('../controllers/authController');
// Login route
router.post('/login', login);
// Register route
router.post('/register', register);
// Logout route
router.post('/logout', logout);
// Get current user
router.get('/me', getCurrentUser);
module.exports = router;

View File

@@ -0,0 +1,23 @@
const express = require('express');
const router = express.Router();
const { getDashboard, createJob, updateJob, deleteJob, getApplications, manageApplication } = require('../controllers/jobProviderController');
// Get job provider dashboard
router.get('/dashboard', getDashboard);
// Create a new job
router.post('/jobs', createJob);
// Update a job
router.put('/jobs/:jobId', updateJob);
// Delete a job
router.delete('/jobs/:jobId', deleteJob);
// Get applications for job provider's jobs
router.get('/applications', getApplications);
// Manage an application
router.put('/applications/:applicationId', manageApplication);
module.exports = router;

View File

@@ -0,0 +1,20 @@
const express = require('express');
const router = express.Router();
const { getProfile, updateProfile, uploadResume, getApplications, applyForJob } = require('../controllers/jobSeekerController');
// Get job seeker profile
router.get('/profile', getProfile);
// Update job seeker profile
router.put('/profile', updateProfile);
// Upload resume
router.post('/resume', uploadResume);
// Get job seeker's applications
router.get('/applications', getApplications);
// Apply for a job
router.post('/apply/:jobId', applyForJob);
module.exports = router;

View File

@@ -0,0 +1,17 @@
const express = require('express');
const router = express.Router();
const { getTenant, createTenant, updateTenant, deleteTenant } = require('../controllers/tenantController');
// Get tenant by ID
router.get('/:tenantId', getTenant);
// Create a new tenant
router.post('/', createTenant);
// Update tenant
router.put('/:tenantId', updateTenant);
// Delete tenant
router.delete('/:tenantId', deleteTenant);
module.exports = router;

View File

@@ -0,0 +1,106 @@
// services/authService.js
const jwt = require('jsonwebtoken');
const bcrypt = require('bcryptjs');
const { v4: uuidv4 } = require('uuid');
const { User } = require('../models'); // Assuming we have a User model
const JWT_SECRET = process.env.JWT_SECRET || 'fallback_secret';
// Mock database - in real implementation, this would be a real database
const users = [];
const login = async (email, password) => {
try {
// Find user by email
const user = users.find(u => u.email === email);
if (!user) {
return { error: 'Invalid email or password' };
}
// Check password
const isPasswordValid = await bcrypt.compare(password, user.passwordHash);
if (!isPasswordValid) {
return { error: 'Invalid email or password' };
}
// Generate JWT token
const token = jwt.sign(
{ userId: user.id, email: user.email, tenantId: user.tenantId },
JWT_SECRET,
{ expiresIn: '24h' }
);
// Return user info and token (excluding password)
return {
user: {
id: user.id,
email: user.email,
firstName: user.firstName,
lastName: user.lastName,
userType: user.userType,
tenantId: user.tenantId
},
token
};
} catch (error) {
console.error('Login service error:', error);
return { error: 'Internal server error' };
}
};
const register = async (email, password, firstName, lastName, userType, tenantId) => {
try {
// Check if user already exists
const existingUser = users.find(u => u.email === email);
if (existingUser) {
return { error: 'User with this email already exists' };
}
// Validate user type
if (!['job-seeker', 'job-provider'].includes(userType)) {
return { error: 'User type must be either job-seeker or job-provider' };
}
// Hash password
const saltRounds = 12;
const passwordHash = await bcrypt.hash(password, saltRounds);
// Create new user
const newUser = {
id: uuidv4(),
email,
passwordHash,
firstName,
lastName,
userType,
tenantId,
createdAt: new Date(),
updatedAt: new Date()
};
users.push(newUser);
// Return user info (excluding password)
return {
user: {
id: newUser.id,
email: newUser.email,
firstName: newUser.firstName,
lastName: newUser.lastName,
userType: newUser.userType,
tenantId: newUser.tenantId
}
};
} catch (error) {
console.error('Registration service error:', error);
return { error: 'Internal server error' };
}
};
module.exports = {
login,
register
};

View File

@@ -0,0 +1,36 @@
// tests/app.test.js
const request = require('supertest');
const app = require('../index');
describe('Main Application Routes', () => {
test('GET / should return welcome message', async () => {
const response = await request(app)
.get('/')
.expect(200);
expect(response.body).toHaveProperty('message');
expect(response.body.message).toBe('Welcome to MerchantsOfHope.org - TSYS Group Recruiting Platform');
expect(response.body).toHaveProperty('status');
expect(response.body.status).toBe('running');
});
test('GET /health should return health status', async () => {
const response = await request(app)
.get('/health')
.expect(200);
expect(response.body).toHaveProperty('status');
expect(response.body.status).toBe('OK');
expect(response.body).toHaveProperty('service');
expect(response.body.service).toBe('MOH Portal API');
});
test('GET /nonexistent should return 404', async () => {
const response = await request(app)
.get('/nonexistent')
.expect(404);
expect(response.body).toHaveProperty('error');
expect(response.body.error).toBe('Route not found');
});
});

View File

@@ -0,0 +1,11 @@
// tests/setup.js
// Setup file for Jest tests
// Mock environment variables
process.env.JWT_SECRET = 'test_secret';
process.env.DB_HOST = 'localhost';
process.env.DB_USER = 'test_user';
process.env.DB_PASSWORD = 'test_password';
process.env.DB_NAME = 'test_db';
console.log('Jest test environment setup complete');