Skip to content

Commit

Permalink
enabled ci-cd and enhanced the counter logic
Browse files Browse the repository at this point in the history
  • Loading branch information
tashfiqul-islam committed Jan 3, 2024
1 parent 470f3a3 commit 8553012
Show file tree
Hide file tree
Showing 8 changed files with 6,110 additions and 593 deletions.
38 changes: 38 additions & 0 deletions .github/workflows/nodejs.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
name: Node.js CI

on:
push:
branches: [master]
pull_request:
branches: [master]

jobs:
test:
runs-on: ubuntu-latest

services:
mongo:
image: mongo
ports:
- 27017:27017

strategy:
matrix:
node-version: [14.x, 16.x]

steps:
- uses: actions/checkout@v2
- name: Set up Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v2
with:
node-version: ${{ matrix.node-version }}
cache: 'npm'

- name: Install dependencies
run: npm ci

- name: Run tests
run: npm test
env:
MONGODB_URI: ${{ secrets.MONGODB_URI }}
PORT: ${{ secrets.PORT }}
80 changes: 80 additions & 0 deletions __tests__/api/view-counter.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
const request = require('supertest');
const { MongoClient } = require('mongodb');
const { MongoMemoryServer } = require('mongodb-memory-server');
const makeApp = require('../../api/view-counter');

// This suite tests the API endpoints of the view-counter application
describe('API /view-counter Tests', () => {
let mongoServer;
let client;
let app;

// Set up an in-memory MongoDB instance before running tests
beforeAll(async () => {
mongoServer = await MongoMemoryServer.create();
const uri = mongoServer.getUri();
console.log('Test MongoDB URI:', uri);
client = new MongoClient(uri);
await client.connect();

// Initialize the app with a test database client
app = makeApp(client);
});

// Clean up and close MongoDB connection after tests are complete
afterAll(async () => {
await client.close();
await mongoServer.stop();
});

// Test for successful view count increment and badge URL generation
test('increments view count and returns badge URL for valid username', async () => {
const username = 'testuser';
const response = await request(app).get('/api/view-counter').query({ username });
expect(response.status).toBe(302);

// Check if the response redirects to the correct badge URL
const badgeUrlRegex = /^https:\/\/custom-icon-badges\.demolab\.com\/static\/v1\?.*/;
expect(response.headers.location).toMatch(badgeUrlRegex);

// Verify the view count in the database
const db = client.db('githubViews');
const result = await db.collection('viewCounts').findOne({ username });
expect(result).not.toBeNull();
expect(result.views).toBe(1);

// Additional logging for debugging purposes
const allDocuments = await db.collection('viewCounts').find({}).toArray();
console.log('All documents in viewCounts collection:', allDocuments);
});

// Test for handling missing username parameter
test('returns 400 Bad Request for missing username', async () => {
const response = await request(app).get('/api/view-counter');
expect(response.status).toBe(400);
});

// Test for handling concurrent requests correctly
test('handles concurrent requests correctly', async () => {
const username = 'concurrentUser';
const requestCount = 5;

// Send multiple concurrent requests
const promises = Array.from({ length: requestCount }, () =>
request(app).get('/api/view-counter').query({ username }),
);

const responses = await Promise.all(promises);
responses.forEach((response) => {
expect(response.status).toBe(302);
expect(response.headers.location).toMatch(
/^https:\/\/custom-icon-badges\.demolab\.com\/static\/v1\?.*/,
);
});

// Verify that the view count matches the number of requests sent
const db = client.db('githubViews');
const result = await db.collection('viewCounts').findOne({ username });
expect(result.views).toBe(requestCount);
});
});
108 changes: 108 additions & 0 deletions __tests__/badgeGenerator.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
const { formatNumberWithCommas, formatLargeNumber, generateBadge } = require('../badgeGenerator');

describe('Badge Generator Tests', () => {
// Tests for formatNumberWithCommas
describe('formatNumberWithCommas function', () => {
// Standard formatting tests
test('formats numbers with commas', () => {
expect(formatNumberWithCommas(1000)).toBe('1,000');
expect(formatNumberWithCommas(1234567)).toBe('1,234,567');
});

// Handling small numbers
test('handles small numbers without commas', () => {
expect(formatNumberWithCommas(999)).toBe('999');
});

// Zero handling
test('handles zero correctly', () => {
expect(formatNumberWithCommas(0)).toBe('0');
});

// Edge case tests for negative numbers and non-numeric values
test('handles negative numbers', () => {
expect(formatNumberWithCommas(-1000)).toBe('-1,000');
});

test('handles non-numeric values', () => {
expect(formatLargeNumber('abcd')).toBe('NaN');
});
});

// Tests for formatLargeNumber
describe('formatLargeNumber function', () => {
// Standard abbreviation tests
test('formats large numbers with abbreviations', () => {
expect(formatLargeNumber(150000)).toBe('150k');
expect(formatLargeNumber(2000000)).toBe('2M');
});

// Handling smaller numbers
test('handles numbers less than 1000 without abbreviation', () => {
expect(formatLargeNumber(999)).toBe('999');
});

// Handling millions and billions
test('handles millions and billions correctly', () => {
expect(formatLargeNumber(1000000)).toBe('1M');
expect(formatLargeNumber(1000000000)).toBe('1B');
});

// Edge case tests for negative numbers, non-numeric values, and floating-point numbers
test('handles negative numbers', () => {
expect(formatLargeNumber(-100000)).toBe('-100,000');
});

test('handles non-numeric values', () => {
expect(formatLargeNumber('abcd')).toBe('NaN');
});

test('handles floating-point numbers', () => {
expect(formatLargeNumber(999.99)).toBe('999.99');
});
});

// Tests for generateBadge
describe('generateBadge function', () => {
// Test for correct badge URL generation for small numbers
test('generates correct badge URL for small numbers', () => {
const count = 150;
const expectedUrl =
'https://custom-icon-badges.demolab.com/static/v1?label=Profile+Visitors&message=150&color=007ec6&style=for-the-badge&logo=github&logoColor=white&logoSource=feather';
expect(generateBadge(count)).toBe(expectedUrl);
});

test('generates correct badge URL for large numbers', () => {
const count = 150000;
const expectedUrl =
'https://custom-icon-badges.demolab.com/static/v1?label=Profile+Visitors&message=150k&color=007ec6&style=for-the-badge&logo=github&logoColor=white&logoSource=feather';
expect(generateBadge(count)).toBe(expectedUrl);
});

// Handling zero views
test('handles zero views correctly', () => {
const count = 0;
const expectedUrl =
'https://custom-icon-badges.demolab.com/static/v1?label=Profile+Visitors&message=0&color=007ec6&style=for-the-badge&logo=github&logoColor=white&logoSource=feather';
expect(generateBadge(count)).toBe(expectedUrl);
});

// Handling very large numbers
test('handles very large numbers correctly', () => {
const count = 1000000000; // 1 billion
const expectedUrl =
'https://custom-icon-badges.demolab.com/static/v1?label=Profile+Visitors&message=1B&color=007ec6&style=for-the-badge&logo=github&logoColor=white&logoSource=feather';
expect(generateBadge(count)).toBe(expectedUrl);
});

// Additional scenario tests for different styles, colors
test('generates badge with custom style and color', () => {
const count = 500;
const style = 'for-the-badge';
const color = '007ec6';
const expectedUrl =
'https://custom-icon-badges.demolab.com/static/v1?label=Profile+Visitors&message=500&color=007ec6&style=for-the-badge&logo=github&logoColor=white&logoSource=feather';
expect(generateBadge(count, style, color)).toBe(expectedUrl);
});
});
});
58 changes: 58 additions & 0 deletions __tests__/config.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
jest.mock('dotenv', () => ({
config: jest.fn().mockImplementation(() => {
process.env.MONGODB_URI = 'mockMongoUri';
process.env.PORT = '3001';
}),
}));

const configModule = require('../config');

describe('Configuration Tests', () => {
const originalEnv = process.env;

beforeEach(() => {
jest.resetModules(); // Reset modules to clear any cached data
process.env = { ...originalEnv }; // Reset environment variables to their original state
});

afterAll(() => {
process.env = originalEnv; // Restore original environment variables after tests
});

// Test loading of MongoDB URI
test('loads correct MongoDB URI', () => {
process.env.MONGODB_URI = 'mockMongoUri';
jest.resetModules(); // Reset modules after changing the environment variable
const config = require('../config'); // Reload the module to reflect the new environment variable
expect(config.mongodb.uri).toBe('mockMongoUri');
});

// Test error for missing MongoDB URI
test('throws error if MONGODB_URI is missing', () => {
delete process.env.MONGODB_URI;
expect(() => configModule.__get__('config')).toThrow();
});

// Test loading of server port
test('loads correct server port', () => {
process.env.PORT = '3001';
jest.resetModules(); // Reset modules after changing the environment variable
const config = require('../config'); // Reload the module to reflect the new environment variable
expect(config.server.port).toBe(3001);
});

// Test defaults to port 3001 if PORT not set
test('defaults to port 3001 if PORT not set', () => {
delete process.env.PORT;
jest.resetModules(); // Reset modules after changing the environment variable
const config = require('../config'); // Reload the module to reflect the new environment variable
expect(config.server.port).toBe(3001);
});

// Test sets rate limiting configuration
test('sets rate limiting configuration', () => {
const config = require('../config'); // Directly require the module
expect(config.rateLimit.windowMs).toBe(60 * 1000);
expect(config.rateLimit.max).toBe(100);
});
});
Loading

0 comments on commit 8553012

Please sign in to comment.