Skip to content

Commit

Permalink
Implement buyer able to leave feedback on products
Browse files Browse the repository at this point in the history
  • Loading branch information
UwicyezaG authored and elijahladdie committed May 31, 2024
1 parent bb96156 commit 0a5c54a
Show file tree
Hide file tree
Showing 19 changed files with 335 additions and 122 deletions.
219 changes: 118 additions & 101 deletions src/__test__/cart.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import { cleanDatabase } from './test-assets/DatabaseCleanup';
const vendor1Id = uuid();
const buyer1Id = uuid();
const buyer2Id = uuid();
const buyer3Id = uuid();
const product1Id = uuid();
const product2Id = uuid();
const catId = uuid();
Expand Down Expand Up @@ -52,7 +53,7 @@ const sampleBuyer1: UserInterface = {
id: buyer1Id,
firstName: 'buyer1',
lastName: 'user',
email: 'elijahladdiedv@gmail.com',
email: 'manger@gmail.com',
password: 'password',
userType: 'Buyer',
gender: 'Male',
Expand All @@ -65,14 +66,26 @@ const sampleBuyer2: UserInterface = {
id: buyer2Id,
firstName: 'buyer1',
lastName: 'user',
email: 'buyer1112@example.com',
email: 'elijahladdiedv@example.com',
password: 'password',
userType: 'Buyer',
gender: 'Male',
phoneNumber: '12116380996348',
photoUrl: 'https://example.com/photo.jpg',
role: 'BUYER',
};
const sampleBuyer3: UserInterface = {
id: buyer3Id,
firstName: 'buyer1',
lastName: 'user',
email: '[email protected]',
password: 'password',
userType: 'Admin',
gender: 'Male',
phoneNumber: '121163800',
photoUrl: 'https://example.com/photo.jpg',
role: 'ADMIN',
};

const sampleCat = {
id: catId,
Expand Down Expand Up @@ -175,7 +188,7 @@ afterAll(async () => {
server.close();
});

describe('Cart management for guest/buyer', () => {
describe('Cart| Order management for guest/buyer', () => {
describe('Creating new product', () => {
it('should create new product', async () => {
const response = await request(app)
Expand Down Expand Up @@ -376,6 +389,108 @@ describe('Cart management for guest/buyer', () => {
});
});

describe('Order management tests', () => {
let orderId: any;
let productId: any;
let feedbackId: any;
let feedback2Id: any;
describe('Create order', () => {
it('should return 400 when user ID is not provided', async () => {
const response = await request(app)
.post('/product/orders')
.send({
address: {
country: 'Test Country',
city: 'Test City',
street: 'Test Street',
},
})
.set('Authorization', `Bearer ${getAccessToken(buyer1Id, sampleBuyer1.email)}`);
expect(response.status).toBe(201);
});

it('should return orders for the buyer', async () => {
const response = await request(app)
.get('/product/client/orders')
.set('Authorization', `Bearer ${getAccessToken(buyer1Id, sampleBuyer1.email)}`);
expect(response.status).toBe(200);
orderId = response.body.data.orders[0]?.id;
productId = response.body.data.orders[0]?.orderItems[0]?.product?.id;
});
it('should return 404 if the buyer has no orders', async () => {
const response = await request(app)
.get('/product/client/orders')
.set('Authorization', `Bearer ${getAccessToken(buyer2Id, sampleBuyer2.email)}`);
expect(response.status).toBe(404);
expect(response.body.message).toBeUndefined;
});

it('should return transaction history for the buyer', async () => {
const response = await request(app)
.get('/product/orders/history')
.set('Authorization', `Bearer ${getAccessToken(buyer1Id, sampleBuyer1.email)}`);
expect(response.status).toBe(200);
expect(response.body.message).toBe('Transaction history retrieved successfully');
});

it('should return 400 when user ID is not provided', async () => {
const response = await request(app)
.get('/product/orders/history')
.set('Authorization', `Bearer ${getAccessToken(buyer1Id, sampleBuyer1.email)}`);
expect(response.status).toBe(200);
});
});

describe('Update order', () => {
it('should update order status successfully', async () => {
const response = await request(app)
.put(`/product/client/orders/${orderId}`)
.send({ orderStatus: 'completed' })
.set('Authorization', `Bearer ${getAccessToken(buyer1Id, sampleBuyer1.email)}`);
expect(response.status).toBe(200);
});
});
describe('Add feedback to the product with order', () => {
it('should create new feedback to the ordered product', async () => {
const response = await request(app)
.post(`/feedback/${productId}/new`)
.send({ orderId, comment: 'Well this product looks so fantastic' })
.set('Authorization', `Bearer ${getAccessToken(buyer1Id, sampleBuyer1.email)}`);
expect(response.status).toBe(201);
feedbackId = response.body.data.id

Check warning on line 460 in src/__test__/cart.test.ts

View workflow job for this annotation

GitHub Actions / build-lint-test-coverage

Missing semicolon

Check warning on line 460 in src/__test__/cart.test.ts

View workflow job for this annotation

GitHub Actions / build-lint-test-coverage

Missing semicolon
});
it('should create new feedback to the ordered product', async () => {
const response = await request(app)
.post(`/feedback/${productId}/new`)
.send({ orderId, comment: 'Murigalike this product looks so fantastic' })
.set('Authorization', `Bearer ${getAccessToken(buyer1Id, sampleBuyer1.email)}`);
expect(response.status).toBe(201);
feedback2Id = response.body.data.id

Check warning on line 468 in src/__test__/cart.test.ts

View workflow job for this annotation

GitHub Actions / build-lint-test-coverage

Missing semicolon

Check warning on line 468 in src/__test__/cart.test.ts

View workflow job for this annotation

GitHub Actions / build-lint-test-coverage

Missing semicolon
});
it('should updated existing feedback successfully', async () => {
const response = await request(app)
.put(`/feedback/update/${feedbackId}`,)
.send({ orderId, comment: 'Well this product looks so lovely' })
.set('Authorization', `Bearer ${getAccessToken(buyer1Id, sampleBuyer1.email)}`);
expect(response.status).toBe(200);
});
it('should remove recorded feedback', async () => {
const response = await request(app)
.delete(`/feedback/delete/${feedbackId}`)
.send({ orderId, comment: 'Well this product looks so lovely' })
.set('Authorization', `Bearer ${getAccessToken(buyer1Id, sampleBuyer1.email)}`);
expect(response.status).toBe(200);
});
it('should remove recorder feedback as admin ', async () => {
const response = await request(app)
.delete(`/feedback/admin/delete/${feedback2Id}`)
.send({ orderId, comment: 'Well this product looks so lovely' })
.set('Authorization', `Bearer ${getAccessToken(buyer3Id, sampleBuyer3.email)}`);
expect(response.status).toBe(401);
});
});
});

describe('Deleting product from cart', () => {
it('should return 404 if product does not exist in cart', async () => {
const response = await request(app)
Expand Down Expand Up @@ -511,101 +626,3 @@ describe('Cart management for guest/buyer', () => {
});
});
});

describe('Order management tests', () => {
let orderId: string | null;
describe('Create order', () => {
it('should return 400 when user ID is not provided', async () => {
const response = await request(app)
.post('/product/orders')
.send({
address: {
country: 'Test Country',
city: 'Test City',
street: 'Test Street',
},
})
.set('Authorization', `Bearer ${getAccessToken(buyer1Id, sampleBuyer1.email)}`);
expect(response.status).toBe(400);
});

it('should create a new order', async () => {
const response = await request(app)
.post('/product/orders')
.send({
address: {
country: 'Test Country',
city: 'Test City',
street: 'Test Street',
},
})
.set('Authorization', `Bearer ${getAccessToken(buyer2Id, sampleBuyer2.email)}`);

expect(response.status).toBe(400);
expect(response.body.message).toBeUndefined;
orderId = response.body.data?.orderId; // Assuming orderId is returned in response
});

it('should insert a new order', async () => {
const response = await request(app)
.post('/product/orders')
.send({
address: {
country: 'Test Country',
city: 'Test City',
street: 'Test Street',
},
})
.set('Authorization', `Bearer ${getAccessToken(buyer2Id, sampleBuyer2.email)}`);

expect(response.status).toBe(400);
expect(response.body.message).toBeUndefined;
orderId = response.body.data?.orderId; // Assuming orderId is returned in response
});
});

describe('Get orders', () => {
it('should return orders for the buyer', async () => {
const response = await request(app)
.get('/product/client/orders')
.set('Authorization', `Bearer ${getAccessToken(buyer2Id, sampleBuyer2.email)}`);
expect(response.status).toBe(404);
expect(response.body.message).toBeUndefined;
});

it('should return 404 if the buyer has no orders', async () => {
const response = await request(app)
.get('/product/client/orders')
.set('Authorization', `Bearer ${getAccessToken(buyer2Id, sampleBuyer2.email)}`);
expect(response.status).toBe(404);
expect(response.body.message).toBeUndefined;
});
});

describe('Get transaction history', () => {
it('should return transaction history for the buyer', async () => {
const response = await request(app)
.get('/product/orders/history')
.set('Authorization', `Bearer ${getAccessToken(buyer1Id, sampleBuyer1.email)}`);
expect(response.status).toBe(404);
expect(response.body.message).toBe('No transaction history found');
});

it('should return 400 when user ID is not provided', async () => {
const response = await request(app)
.get('/product/orders/history')
.set('Authorization', `Bearer ${getAccessToken(buyer1Id, sampleBuyer1.email)}`);
expect(response.status).toBe(404);
});
});

describe('Update order', () => {
it('should update order status successfully', async () => {
const response = await request(app)
.put(`/product/client/orders/${orderId}`)
.send({ orderStatus: 'delivered' })
.set('Authorization', `Bearer ${getAccessToken(buyer1Id, sampleBuyer1.email)}`);
expect(response.status).toBe(500);
});
});
});
2 changes: 2 additions & 0 deletions src/__test__/test-assets/DatabaseCleanup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,13 @@ import { User } from '../../entities/User';
import { server } from '../..';
import { VendorOrderItem } from '../../entities/VendorOrderItem';
import { VendorOrders } from '../../entities/vendorOrders';
import { Feedback } from '../../entities/Feedback';

export const cleanDatabase = async () => {
const connection = getConnection();

// Delete from child tables first
await connection.getRepository(Feedback).delete({});
await connection.getRepository(Transaction).delete({});
await connection.getRepository(Coupon).delete({});
await connection.getRepository(VendorOrderItem).delete({});
Expand Down
21 changes: 21 additions & 0 deletions src/controllers/feedbackController.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { Request, Response } from 'express';
import { createFeedbackService } from '../services/feedbackServices/createFeedback';
import { updateFeedbackService } from '../services/feedbackServices/updateFeedback';
import { deleteFeedbackService } from '../services/feedbackServices/deleteFeedback';
import { adminDeleteFeedbackService } from '../services/feedbackServices/adminDeleteFeedback';

export const createFeedback = async (req: Request, res: Response) => {
await createFeedbackService(req, res);
};

export const updateFeedback = async (req: Request, res: Response) => {
await updateFeedbackService(req, res);
};

export const deleteFeedback = async (req: Request, res: Response) => {
await deleteFeedbackService(req, res);
};

export const adminDeleteFeedback = async (req: Request, res: Response) => {
await adminDeleteFeedbackService(req, res);
};
30 changes: 30 additions & 0 deletions src/entities/Feedback.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { Entity, PrimaryGeneratedColumn, Column, ManyToOne, CreateDateColumn, UpdateDateColumn } from 'typeorm';
import { User } from './User';
import { Product } from './Product';
import { IsNotEmpty } from 'class-validator';
import { Order } from './Order';

@Entity()
export class Feedback {
@PrimaryGeneratedColumn('uuid')
@IsNotEmpty()
id!: string;

@Column('text')
comment!: string;

@ManyToOne(() => User, user => user.feedbacks)
user!: User;

@ManyToOne(() => Product, product => product.feedbacks)
product!: Product;

@ManyToOne(() => Order, order => order.feedbacks)
order!: Order;

@CreateDateColumn()
createdAt!: Date;

@UpdateDateColumn()
updatedAt!: Date;
}
5 changes: 5 additions & 0 deletions src/entities/Order.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { IsNotEmpty, IsNumber, IsDate, IsIn } from 'class-validator';
import { User } from './User';
import { OrderItem } from './OrderItem';
import { Transaction } from './transaction';
import { Feedback } from './Feedback';

@Entity()
export class Order {
Expand All @@ -33,6 +34,10 @@ export class Order {

@OneToMany(() => Transaction, transaction => transaction.order)
transactions!: Transaction[];

@OneToMany(() => Feedback, feedback => feedback.order)
feedbacks!: Feedback[];

@Column({ default: 'order placed' })
@IsNotEmpty()
@IsIn([
Expand Down
5 changes: 4 additions & 1 deletion src/entities/Product.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,12 @@ import { Order } from './Order';
import { Coupon } from './coupon';
import { OrderItem } from './OrderItem';
import { VendorOrderItem } from './VendorOrderItem';
import { Feedback } from './Feedback';

@Entity()
@Unique(['id'])
export class Product {
static query () {
static query() {
throw new Error('Method not implemented.');
}
@PrimaryGeneratedColumn('uuid')
Expand All @@ -39,6 +40,8 @@ export class Product {

@OneToMany(() => VendorOrderItem, vendorOrderItems => vendorOrderItems.product)
vendorOrderItems!: VendorOrderItem[];
@OneToMany(() => Feedback, feedback => feedback.product)
feedbacks!: Feedback[];

@OneToOne(() => Coupon, (coupons: any) => coupons.product)
@JoinColumn()
Expand Down
3 changes: 3 additions & 0 deletions src/entities/User.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import { IsEmail, IsNotEmpty, IsString, IsBoolean, IsIn } from 'class-validator'
import { roles } from '../utils/roles';
import { Order } from './Order';
import { Transaction } from './transaction';
import { Feedback } from './Feedback';

export interface UserInterface {
id?: string;
Expand Down Expand Up @@ -111,6 +112,8 @@ export class User {

@Column({ type: 'numeric', precision: 24, scale: 2, default: 0 })
accountBalance!: number;
@OneToMany(() => Feedback, feedback => feedback.product)
feedbacks!: Feedback[];

@BeforeInsert()
setRole (): void {
Expand Down
Loading

0 comments on commit 0a5c54a

Please sign in to comment.