feat: implement customer profile management and business application features
- Added customer profile controller with endpoints for retrieving, updating, and deactivating user profiles. - Introduced address model and routes for managing user addresses. - Created business application model and service for handling business applications, including approval and rejection processes. - Developed business contact and credit account models to support business customer functionalities. - Implemented settlement term model for managing payment terms. - Added email templates for business application notifications (approved, rejected, received, and status changes). - Enhanced validation schemas for customer and business inputs to ensure data integrity. - Created unit tests for business application service and validation schemas to ensure functionality and correctness. - Added migration scripts to set up new database tables and columns for customer and business features.
This commit is contained in:
@@ -0,0 +1,11 @@
|
||||
describe("business application service", () => {
|
||||
beforeEach(() => jest.resetModules());
|
||||
const setup = ({ active = null } = {}) => {
|
||||
const created={id:"app-1",user_id:"usr-1",status:"PENDING"};
|
||||
const models={sequelize:{transaction:jest.fn(async cb=>cb({LOCK:{UPDATE:"UPDATE"}}))},User:{findByPk:jest.fn().mockResolvedValue({id:"usr-1",accountStatus:"ACTIVE",emailVerifiedAt:new Date(),accountType:"customer"})},BusinessApplication:{findOne:jest.fn().mockResolvedValue(active),create:jest.fn().mockResolvedValue(created)}};
|
||||
jest.doMock("../../app/models",()=>models);jest.doMock("../../app/utils/referenceNumber.util",()=>({nextSequence:jest.fn()}));jest.doMock("../../app/services/auth/session.service",()=>({revokeAllUserSessions:jest.fn()}));
|
||||
return {service:require("../../app/services/business/business.service"),models,created};
|
||||
};
|
||||
test("verified active customer can submit an application",async()=>{const {service,models,created}=setup();const result=await service.apply("usr-1",{businessName:"Acme",legalName:"Acme Ltd",registrationNumber:"PV1",businessType:"Retail",contactEmail:"a@example.com",contactPhone:"+94770000000"});expect(result).toBe(created);expect(models.BusinessApplication.create).toHaveBeenCalledWith(expect.objectContaining({user_id:"usr-1",status:"PENDING"}),expect.anything());});
|
||||
test("duplicate active application is prevented",async()=>{const {service}=setup({active:{id:"existing"}});await expect(service.apply("usr-1",{})).rejects.toMatchObject({code:"ACTIVE_APPLICATION_EXISTS",status:409});});
|
||||
});
|
||||
@@ -0,0 +1,42 @@
|
||||
const customer = require("../../app/validation/customer.schemas");
|
||||
const business = require("../../app/validation/business.schemas");
|
||||
const wrap = (body) => ({ body, params: {}, query: {} });
|
||||
|
||||
describe("Phase 3 strict customer and business inputs", () => {
|
||||
test("accepts supported locales and rejects unsupported locales", () => {
|
||||
expect(customer.profileUpdate.safeParse(wrap({ preferredLanguage: "si" })).success).toBe(true);
|
||||
expect(customer.profileUpdate.safeParse(wrap({ preferredLanguage: "fr" })).success).toBe(false);
|
||||
});
|
||||
test.each(["accountType", "accountStatus", "roles", "permissions", "tokenVersion", "creditLimit", "partnerId"])("profile rejects %s mass assignment", (field) => {
|
||||
expect(customer.profileUpdate.safeParse(wrap({ [field]: "unsafe" })).success).toBe(false);
|
||||
});
|
||||
test("validates and normalizes an international address", () => {
|
||||
const result = customer.addressCreate.safeParse(wrap({ label:" Home ",recipientName:" Asha Perera ",phoneNumber:"+94 77 000 0000",addressLine1:" 1 Main Road ",city:"Colombo",countryCode:"lk" }));
|
||||
expect(result.success).toBe(true); expect(result.data.body.countryCode).toBe("LK"); expect(result.data.body.recipientName).toBe("Asha Perera");
|
||||
});
|
||||
test("address rejects arbitrary fields", () => {
|
||||
expect(customer.addressCreate.safeParse(wrap({ label:"Home",recipientName:"Asha",phoneNumber:"+94770000000",addressLine1:"Road",city:"Colombo",countryCode:"LK",userId:"other" })).success).toBe(false);
|
||||
});
|
||||
test("deactivation requires explicit confirmation", () => {
|
||||
expect(customer.deactivate.safeParse(wrap({ confirmation:"DEACTIVATE" })).success).toBe(true);
|
||||
expect(customer.deactivate.safeParse(wrap({ confirmation:"yes" })).success).toBe(false);
|
||||
});
|
||||
test("business applications reject approval and identity fields", () => {
|
||||
const base={businessName:"Acme",legalName:"Acme Ltd",registrationNumber:"PV-1",businessType:"Retail",contactEmail:"owner@example.com",contactPhone:"+94770000000"};
|
||||
expect(business.application.safeParse(wrap(base)).success).toBe(true);
|
||||
expect(business.application.safeParse(wrap({...base,status:"APPROVED",accountType:"business_customer"})).success).toBe(false);
|
||||
});
|
||||
test("business self-update rejects partner, approval, and credit fields", () => {
|
||||
expect(business.businessUpdate.safeParse(wrap({ businessName:"New Name" })).success).toBe(true);
|
||||
expect(business.businessUpdate.safeParse(wrap({ partnerId:"ZUM-BIZ-9" })).success).toBe(false);
|
||||
expect(business.businessUpdate.safeParse(wrap({ creditLimit:5000 })).success).toBe(false);
|
||||
});
|
||||
test("credit configuration rejects negative values and normalizes currency", () => {
|
||||
expect(business.credit.safeParse(wrap({creditLimit:-1,currency:"lkr",status:"ACTIVE"})).success).toBe(false);
|
||||
const result=business.credit.safeParse(wrap({creditLimit:1000.25,currency:"lkr",status:"ACTIVE"}));expect(result.success).toBe(true);expect(result.data.body.currency).toBe("LKR");
|
||||
});
|
||||
test("rejection requires a reason", () => {
|
||||
expect(business.reject.safeParse(wrap({reason:"Insufficient registration evidence"})).success).toBe(true);
|
||||
expect(business.reject.safeParse(wrap({})).success).toBe(false);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user