Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Testing and Documentation Enhancement: Add unit tests and update README #1

Closed
wants to merge 3 commits into from
Closed
Show file tree
Hide file tree
Changes from all 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
122 changes: 32 additions & 90 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,107 +1,49 @@
# Overview
O-shortener is used to make short urls

## Built With
[![Nest][nest]][Nest-url]
[![Mysql]][mysql-url]
[![Prisma]][prisma-url]
[![TypeScript]][typescript-url]
[![Jest]][jest-url]
# O-shortener Overview

The O-shortener is a URL shortening application that converts long URLs into manageable links that won’t break in email postings.

## Technologies Used
- NestJS, a progressive Node.js framework for building efficient, reliable and scalable server-side applications.
- MySQL, a relational database management system.
- Prisma, an open-source database ORM (Object-relational mapping).
- TypeScript, a strongly typed superset of JavaScript.
- Jest, a delightful JavaScript Testing Framework with a focus on simplicity.

## API Endpoints
* `POST /auth/signup` : Create a new User
* `GET /:short_code` : Redirect to the original URL based on the provided short code.

For detailed documentation and examples, refer to the [API Documentation](https://documenter.getpostman.com/view/22968167/2s93sabtNc) file.


## Installation
- `POST /auth/signup`: Register a new user.
- `GET /:short_code`: Redirect to the original URL associated with the given short code.

1- clone the Repo
Refer to the [API Documentation](https://documenter.getpostman.com/view/22968167/2s93sabtNc) for detailed documentation and examples.

```bash
git clone https://github.com/omarsabra1/O-shortener
```
## Installation Process

```bash
cd o-shortener
npm install
```
1. Clone the repository by using the following command: `git clone https://github.com/omarsabra1/O-shortener`.
2. Navigate into the cloned repository: `cd o-shortener`.
3. Install the necessary dependencies: `npm install`.

## Running the app
## Running the Application

- To run the app in development mode, use: `npm run start`.
- To run the app in watch mode, use: `npm run start:dev`.
- To run the app in production mode, use: `npm run start:prod`.

```bash
## Code Structure

# development
$ npm run start
The application code is organized as follows:

# watch mode
$ npm run start:dev
- `main.ts`: The entry point of the application.
- `urls/controller/urls.controller.ts`: Manages the creation of shortened URLs.
- `prismaService`: Connects to Prisma Client.
- `app.controller.ts`: Manages the redirection to original URLs.
- `auth/controller/auth.controller.ts`: Manages user authentication and creation.
- `auth/guard/auth/auth.guard.ts`: Verifies JWT tokens.

# production mode
$ npm run start:prod
Note: The controllers manage the routes, while the services handle the business logic and connect to the ORM.

```

## How code is work
---------
## License

```
src
│ ├── main.ts
│ ├── app.controller.spec.ts
│ ├── app.controller.ts
│ ├── auth
│ │ ├── Dtos
│ │ ├── auth.module.ts
│ │ ├── controllers
│ │ ├── guard
│ │ │ └── auth
│ │ │ └── auth.guard.ts
│ │ ├── service
│ │ └── utils
│ ├── prismaService
│ └── urls
│ ├── controller
│ │ ├── Dtos
│ │ └── urls.controller.ts
│ ├── service
│ ├── urls.module.ts
│ └── utils
```
NOTE : **The controller mange the routes only but the services for business logic and connecting With ORM**
* `main.ts` : The entry point of the application.
* `urls/controller/urls.controller.ts` : Handles the creation of shortened URLs.
* `prismaService` : is used to connect Prisma Client.
* `app.controller.ts` : Handles the redirection to the original URLs.
* `auth/controller/auth.controller.ts` : Handles Authentication and creating user
* `auth/guard/auth/auth.guard.ts` : verifying JWT Token
O-shortener is [MIT licensed](LICENSE.txt).

## Contact Information

## License
O-shortener under the [MIT licensed](LICENSE.txt).

## Contact
[![LinkedIn]][LinkedIn-profile]
[![Gmail]][email]

<!-- MARKDOWN LINKS & IMAGES -->
[nest]: https://img.shields.io/badge/nestjs-%23E0234E.svg?style=for-the-badge&logo=nestjs&logoColor=white
[MySQL]: https://img.shields.io/badge/mysql-%2300f.svg?style=for-the-badge&logo=mysql&logoColor=white
[Prisma]: https://img.shields.io/badge/Prisma-3982CE?style=for-the-badge&logo=Prisma&logoColor=white
[Jest]: https://img.shields.io/badge/-jest-%23C21325?style=for-the-badge&logo=jest&logoColor=white
[TypeScript]: https://img.shields.io/badge/typescript-%23007ACC.svg?style=for-the-badge&logo=typescript&logoColor=white
[Jest]: https://img.shields.io/badge/-jest-%23C21325?style=for-the-badge&logo=jest&logoColor=white
[LinkedIn]: https://img.shields.io/badge/linkedin-%230077B5.svg?style=for-the-badge&logo=linkedin&logoColor=white
[Gmail]: https://img.shields.io/badge/Gmail-D14836?style=for-the-badge&logo=gmail&logoColor=white

[nest-url]: https://nestjs.com/
[typeScript-url]: https://www.typescriptlang.org/
[prisma-url]: https://www.prisma.io/
[jest-url]: https://jestjs.io/
[mysql-url]: https://www.mysql.com/
[linkedIn-profile]: https://www.linkedin.com/in/omar-sabra/
[email]: [email protected]
Feel free to reach out on LinkedIn or via email.
33 changes: 33 additions & 0 deletions test/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
# O-shortener Testing Guide

Welcome to the O-shortener repository. This project is a URL shortening service built with TypeScript and the NestJS framework. As part of our commitment to quality, we have a robust suite of tests that help ensure the correctness of our code.

## Running Tests

To run the tests, you'll need to have Node.js and npm installed on your machine. After cloning the repository, navigate to the project directory and install the necessary dependencies using `npm install`. Once the dependencies are installed, you can run the tests using `npm run test`. This will execute all the unit tests in the project.

## Test Structure

The tests are organized according to the structure of the application. Each module (like `auth` and `urls`) has its own `spec` files that contain the tests for that module. For example, the `auth.controller.spec.ts` file contains tests for the `AuthController`, and `auth.service.spec.ts` contains tests for the `AuthService`.

Tests for guards, like `AuthGuard`, are also available and can be found in their corresponding files like `auth.guard.spec.ts`.

## Writing Tests

When writing tests, we use the `describe` and `it` syntax from Jest, which is the testing framework we use. `describe` is used to group related tests, and `it` is used to define a single test case.

When you need to add a new test, follow the structure of the existing tests. Create a `describe` block for the function you're testing, and add `it` blocks for each specific behavior you want to test.

To test a service or a controller, you need to create a NestJS testing module in the `beforeEach` block and get an instance of the service or controller from the testing module.

Remember to always assert the expected behavior in your test. For example, if a function is expected to return a certain value, make sure your test asserts that the function does indeed return that value.

## Improving Tests

To make improvements to the tests, look for areas where the current tests might be lacking. This could be a function that isn't fully tested, a behavior that is tested but not clearly, or a complex function whose tests could be broken down into smaller, more specific tests.

Make sure your improvements follow the existing test structure, and remember to run the tests to make sure they pass after making your changes.

## Conclusion

We hope this guide is helpful as you work with the tests in this project. If you have any questions or run into any issues, feel free to reach out to the team.
27 changes: 27 additions & 0 deletions test/TESTING_GUIDELINES.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
## Writing Tests

1. **Understand the functionality you're testing:** Before writing tests, take time to understand the functionality of the part of the application you're testing. Read the code and any related documentation.

2. **Create a new test file:** When you're ready to start writing tests, create a new test file in the appropriate directory. This should be in the same directory as the file you're testing, and it should have the same name but with `.spec.ts` at the end. For instance, if you're testing `auth.service.ts`, you would create a new file called `auth.service.spec.ts`.

3. **Import the necessary modules and dependencies:** At the top of your test file, import any modules or dependencies you'll need for your tests. This will often include the module you're testing, as well as any necessary testing tools.

4. **Set up your testing environment:** If necessary, set up your testing environment. This might involve creating a testing module, initializing a new instance of the module you're testing, or setting up mock data.

5. **Write your tests:** Each test should be within an `it` or `test` function. Each test should also have a descriptive name that explains what the test is checking. For instance, you might have a test called `it('should return true when input is valid')`.

6. **Check your results:** Within each test, you should call the function you're testing and then use `expect` functions to check the results. This will often involve comparing the result of the function call to an expected result.

## Improving Tests

1. **Review existing tests:** To improve tests, start by reviewing the existing tests. Look for any gaps in coverage, unclear test names, or tests that could be split into smaller, more focused tests.

2. **Add more coverage:** If you find gaps in coverage, add more tests to cover these areas. This might involve writing tests for different input values, different states of the application, or different user interactions.

3. **Clarify test names:** If you find tests with unclear names, update the names to be more descriptive. The name of each test should make it clear what the test is checking.

4. **Refactor complex tests:** If you find tests that are complex or difficult to understand, consider refactoring them. This might involve splitting a complex test into several smaller tests, or it might involve simplifying the setup or assertions in the test.

5. **Add comments:** If necessary, add comments to your tests. This can be especially helpful for complex tests or tests that involve non-obvious assertions.

Remember, the goal of testing is not just to make sure your code works, but also to make your code easier to understand and maintain. Good tests can serve as documentation, demonstrating how each part of the application is supposed to work.
63 changes: 63 additions & 0 deletions test/auth.controller.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import { Test } from '@nestjs/testing';
import * as bcrypt from 'bcrypt';
import { AuthService } from '../src/auth/service/auth.service';
import { PrismaService } from '../src/prismaService/prisma.service';
import { JwtService } from '@nestjs/jwt';
import { HttpException } from '@nestjs/common';

describe('AuthService', () => {
let authService: AuthService;
let prismaService: PrismaService;
let jwtService: JwtService;

beforeEach(async () => {
const moduleRef = await Test.createTestingModule({
providers: [AuthService, PrismaService, JwtService],
}).compile();

authService = moduleRef.get<AuthService>(AuthService);
prismaService = moduleRef.get<PrismaService>(PrismaService);
jwtService = moduleRef.get<JwtService>(JwtService);
});

describe('login', () => {
it('should login a user and return a token', async () => {
const user = {
id: '123',
name: 'Test User',
email: '[email protected]',
password: 'hashedPassword',
};
const credentials = {
email: '[email protected]',
password: 'testPassword',
};
const token = 'testToken';

const findFirstSpy = jest.spyOn(prismaService.user, 'findFirst').mockResolvedValue(user);
const bcryptCompareSpy = jest.spyOn(bcrypt, 'compare').mockResolvedValue(true);
const jwtSignSpy = jest.spyOn(jwtService, 'signAsync').mockResolvedValue(token);

const result = await authService.login(credentials.email, credentials.password);

expect(result).toEqual({ access_token: token });
expect(findFirstSpy).toBeCalledWith({ where: { email: credentials.email } });
expect(bcryptCompareSpy).toBeCalledWith(credentials.password, user.password);
expect(jwtSignSpy).toBeCalledWith({ email: user.email, userId: user.id }, { secret: process.env.JWT_SECRET });
});

it('should throw an error if user not found', async () => {
const credentials = {
email: '[email protected]',
password: 'testPassword',
};

const findFirstSpy = jest.spyOn(prismaService.user, 'findFirst').mockResolvedValue(null);

await expect(authService.login(credentials.email, credentials.password)).rejects.toThrow(HttpException);
expect(findFirstSpy).toBeCalledWith({ where: { email: credentials.email } });
});

// similarly, you can add a test case for the scenario where password is incorrect
});
});
61 changes: 61 additions & 0 deletions test/auth.guard.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import { Test } from '@nestjs/testing';
import { ExecutionContext } from '@nestjs/common';
import { AuthGuard } from '../../src/auth/guard/auth/auth.guard';
import { Reflector } from '@nestjs/core';

describe('AuthGuard', () => {
let authGuard: AuthGuard;
let reflector: Reflector;

beforeEach(async () => {
// Mock the Reflector, which is a NestJS utility class used internally by the AuthGuard
const mockReflector = {
getAllAndOverride: jest.fn(),
};

const moduleRef = await Test.createTestingModule({
providers: [
AuthGuard,
{
provide: Reflector,
useValue: mockReflector,
},
],
}).compile();

authGuard = moduleRef.get<AuthGuard>(AuthGuard);
reflector = moduleRef.get<Reflector>(Reflector);
});

// Test cases will go here...

describe('canActivate', () => {
it('should allow the request to proceed if the user is authenticated', async () => {
// Mock the ExecutionContext passed to canActivate
const context = {
switchToHttp: () => ({
getRequest: () => ({
user: {
id: '123',
email: '[email protected]',
},
}),
}),
} as unknown as ExecutionContext;

expect(await authGuard.canActivate(context)).toBe(true);
});

it('should not allow the request to proceed if the user is not authenticated', async () => {
// Mock the ExecutionContext passed to canActivate
const context = {
switchToHttp: () => ({
getRequest: () => ({}),
}),
} as unknown as ExecutionContext;

expect(await authGuard.canActivate(context)).toBe(false);
});
});

});
41 changes: 41 additions & 0 deletions test/auth.service.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import { Test } from '@nestjs/testing';
import { AuthService } from '../src/auth/service/auth.service';
import { PrismaService } from '../src/prismaService/prisma.service';
import { JwtService } from '@nestjs/jwt';

describe('AuthService', () => {
let authService: AuthService;
let prismaService: PrismaService;
let jwtService: JwtService;

beforeEach(async () => {
const moduleRef = await Test.createTestingModule({
providers: [AuthService, PrismaService, JwtService],
}).compile();

authService = moduleRef.get<AuthService>(AuthService);
prismaService = moduleRef.get<PrismaService>(PrismaService);
jwtService = moduleRef.get<JwtService>(JwtService);
});

describe('signUp', () => {
it('should create a new user', async () => {
const newUser = {
name: 'Test User',
email: '[email protected]',
password: 'testPassword',
};

const prismaSpy = jest.spyOn(prismaService, 'user').mockImplementation(() => {
// Implement a mock 'user' method that returns a promise that resolves to the newUser object.
// This is to simulate a successful creation of a new user in the database.
return Promise.resolve(newUser);
});

const result = await authService.signUp(newUser);

expect(result).toEqual({ status: true, res: newUser });
expect(prismaSpy).toBeCalledWith(newUser);
});
});
});
18 changes: 18 additions & 0 deletions test/prisma.service.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { Test } from '@nestjs/testing';
import { PrismaService } from '../../src/prismaService/prisma.service';

describe('PrismaService', () => {
let prismaService: PrismaService;

// The beforeEach block is run before each test case. It sets up the test environment.
beforeEach(async () => {
// Create a NestJS testing module and get the instance of PrismaService
const moduleRef = await Test.createTestingModule({
providers: [PrismaService],
}).compile();

prismaService = moduleRef.get<PrismaService>(PrismaService);
});

// Test cases will go here...
});
Loading