diff --git a/src/models/MotRecord.ts b/src/models/MotRecord.ts index 9a50469..67a208c 100644 --- a/src/models/MotRecord.ts +++ b/src/models/MotRecord.ts @@ -36,11 +36,11 @@ export class MotRecord extends BaseModel implements Partial { constructor(data?: any) { super(data); - this.completedDate = data?.attributes.completed_date ?? data?.completedDate; - this.dataSource = data?.attributes.data_source ?? data?.dateSource; - this.defects = data?.attributes.defects ?? data?.defects; - this.expiryDate = data?.attributes.expiry_date ?? data?.expiryDate; - this.odometer = data?.attributes.odometer ?? data?.odometer; - this.result = data?.attributes.result ?? data?.result; + this.completedDate = data?.attributes?.completed_date ?? data?.completedDate ?? ''; + this.dataSource = data?.attributes?.data_source ?? data?.dataSource ?? ''; + this.defects = data?.attributes?.defects ?? data?.defects ?? []; + this.expiryDate = data?.attributes?.expiry_date ?? data?.expiryDate ?? ''; + this.odometer = data?.attributes?.odometer ?? data?.odometer ?? this.odometer; + this.result = data?.attributes?.result ?? data?.result ?? ''; } } diff --git a/tests/Client.test.ts b/tests/Client.test.ts new file mode 100644 index 0000000..e725478 --- /dev/null +++ b/tests/Client.test.ts @@ -0,0 +1,173 @@ +import { describe, it, expect, mock, beforeEach, afterEach } from "bun:test"; +import { Client } from "../src/Client"; +import { RequestOptions } from "../src/utils/RequestOptions"; + +describe('Client HTTP Methods', () => { + let client: Client; + let mockFetch: any; + const originalFetch = global.fetch; + + const mockResponse = { + ok: true, + status: 200, + headers: new Headers(), + json: () => Promise.resolve({ data: { id: 'test-id', type: 'test-type' } }) + }; + + beforeEach(() => { + // Setup mock fetch + mockFetch = mock(() => Promise.resolve(mockResponse)); + global.fetch = mockFetch; + + // Initialize client with test config + client = new Client({ + organisationId: "test-org", + baseDomain: "https://test-api.com", + clientId: "test-client", + clientSecret: "test-secret", + authDomain: "https://test-auth.com" + }); + + // Set token directly to skip authentication + client.bearerToken = "test-token"; + }); + + afterEach(() => { + global.fetch = originalFetch; + }); + + // Test PATCH request + it('should make PATCH requests with correct parameters', async () => { + const testBody = { data: { type: 'test-type', attributes: { name: 'Test' } } }; + const requestOptions = new RequestOptions({ limit: 10 }); + + await client.makePatchRequest("/test-endpoint", testBody, requestOptions); + + expect(mockFetch).toHaveBeenCalledWith( + "https://test-api.com/test-endpoint?limit=10", + expect.objectContaining({ + method: 'PATCH', + headers: expect.objectContaining({ + 'Content-Type': 'application/json', + 'Authorization': 'Bearer test-token' + }), + credentials: 'include', + body: JSON.stringify(testBody) + }) + ); + }); + + // Test POST request + it('should make POST requests with correct parameters', async () => { + const testBody = { data: { type: 'test-type', attributes: { name: 'Test' } } }; + + await client.makePostRequest("/test-endpoint", testBody); + + expect(mockFetch).toHaveBeenCalledWith( + "https://test-api.com/test-endpoint", + expect.objectContaining({ + method: 'POST', + headers: expect.objectContaining({ + 'Content-Type': 'application/json', + 'Authorization': 'Bearer test-token' + }), + credentials: 'include', + body: JSON.stringify(testBody) + }) + ); + }); + + // Test DELETE request + it('should make DELETE requests with correct parameters', async () => { + await client.makeDeleteRequest("/test-endpoint"); + + expect(mockFetch).toHaveBeenCalledWith( + "https://test-api.com/test-endpoint", + expect.objectContaining({ + method: 'DELETE', + headers: expect.objectContaining({ + 'Content-Type': 'application/json', + 'Authorization': 'Bearer test-token' + }), + credentials: 'include' + }) + ); + }); + + // Test error handling in fetch requests + it('should handle network errors in requests', async () => { + global.fetch = mock(() => Promise.reject(new Error("Network error"))); + + const response = await client.makeGetRequest("/test-endpoint"); + + expect(response.ok).toBe(false); + expect(response.errors.network).toHaveLength(1); + expect(response.errors.network[0].message).toBe("Network error"); + }); + + // Test JSON parse error handling + it('should handle JSON parsing errors', async () => { + global.fetch = mock(() => Promise.resolve({ + ok: true, + status: 200, + headers: new Headers(), + json: () => Promise.reject(new Error("JSON parse error")) + })); + + const response = await client.makeGetRequest("/test-endpoint"); + + expect(response.ok).toBe(false); + expect(response.errors.network).toHaveLength(1); + expect(response.errors.network[0].message).toBe("JSON parse error"); + }); +}); + +describe('Client Service Factory Methods', () => { + let client: Client; + + beforeEach(() => { + client = new Client({ + organisationId: "test-org", + baseDomain: "https://test-api.com" + }); + }); + + // Test all service factory methods + it('should create all service instances', () => { + expect(client.appointments()).toBeDefined(); + expect(client.contacts()).toBeDefined(); + expect(client.customerInteractions()).toBeDefined(); + expect(client.equipment()).toBeDefined(); + expect(client.equipmentCategories()).toBeDefined(); + expect(client.equipmentManufacturers()).toBeDefined(); + expect(client.equipmentModels()).toBeDefined(); + expect(client.formCategories()).toBeDefined(); + expect(client.forms()).toBeDefined(); + expect(client.operationTemplates()).toBeDefined(); + expect(client.organisationMembers()).toBeDefined(); + expect(client.organisations()).toBeDefined(); + expect(client.permissions()).toBeDefined(); + expect(client.properties()).toBeDefined(); + expect(client.roles()).toBeDefined(); + expect(client.schemeTemplates()).toBeDefined(); + expect(client.schemes()).toBeDefined(); + expect(client.serviceAccountKeys()).toBeDefined(); + expect(client.serviceAccounts()).toBeDefined(); + expect(client.submissions()).toBeDefined(); + expect(client.vehicleCategories()).toBeDefined(); + expect(client.vehicleInspections()).toBeDefined(); + expect(client.vehicleInventoryChecks()).toBeDefined(); + expect(client.vehicleManufacturers()).toBeDefined(); + expect(client.vehicleModelSpecifications()).toBeDefined(); + expect(client.vehicleModels()).toBeDefined(); + expect(client.workOrderTemplates()).toBeDefined(); + + expect(client.customerAccounts("customer-1")).toBeDefined(); + expect(client.equipmentExposures("equipment-1")).toBeDefined(); + expect(client.groups("group-1")).toBeDefined(); + expect(client.operations("scheme-1", "work-order-1")).toBeDefined(); + expect(client.teams("team-1")).toBeDefined(); + expect(client.vehicles("vehicle-1")).toBeDefined(); + expect(client.workOrders("scheme-1")).toBeDefined(); + }); +}); \ No newline at end of file diff --git a/tests/ClientConfig.test.ts b/tests/ClientConfig.test.ts new file mode 100644 index 0000000..20a5437 --- /dev/null +++ b/tests/ClientConfig.test.ts @@ -0,0 +1,72 @@ +import { describe, it, expect } from "bun:test"; +import { ClientConfig, ClientConfigInterface } from "../src/ClientConfig"; + +describe('ClientConfig', () => { + it('should initialize with the provided config values', () => { + const configInterface: ClientConfigInterface = { + organisationId: 'test-org', + baseDomain: 'https://custom-domain.com', + clientId: 'test-client-id', + clientSecret: 'test-client-secret', + authDomain: 'https://custom-auth.com' + }; + + const config = new ClientConfig(configInterface); + + expect(config.organisationId).toBe('test-org'); + expect(config.baseDomain).toBe('https://custom-domain.com'); + expect(config.clientId).toBe('test-client-id'); + expect(config.clientSecret).toBe('test-client-secret'); + expect(config.authDomain).toBe('https://custom-auth.com'); + }); + + it('should set default values when optional properties are missing', () => { + const configInterface: ClientConfigInterface = { + organisationId: 'test-org' + }; + + const config = new ClientConfig(configInterface); + + expect(config.organisationId).toBe('test-org'); + expect(config.baseDomain).toBe('https://app.ctrl-hub.com'); + expect(config.clientId).toBe(''); + expect(config.clientSecret).toBe(''); + expect(config.authDomain).toBe('https://auth.ctrl-hub.com'); + }); + + it('should use provided domains when specified', () => { + const configInterface: ClientConfigInterface = { + organisationId: 'test-org', + baseDomain: 'https://staging.ctrl-hub.com', + authDomain: 'https://staging-auth.ctrl-hub.com' + }; + + const config = new ClientConfig(configInterface); + + expect(config.baseDomain).toBe('https://staging.ctrl-hub.com'); + expect(config.authDomain).toBe('https://staging-auth.ctrl-hub.com'); + }); + + it('should handle partial auth credentials', () => { + const configInterface: ClientConfigInterface = { + organisationId: 'test-org', + clientId: 'test-client-id', + }; + + const config = new ClientConfig(configInterface); + + expect(config.clientId).toBe('test-client-id'); + expect(config.clientSecret).toBe(''); + + const configInterface2: ClientConfigInterface = { + organisationId: 'test-org', + clientSecret: 'test-client-secret' + }; + + const config2 = new ClientConfig(configInterface2); + + expect(config2.clientId).toBe(''); + expect(config2.clientSecret).toBe('test-client-secret'); + }); + +}); \ No newline at end of file diff --git a/tests/models/Appointment.test.ts b/tests/models/Appointment.test.ts new file mode 100644 index 0000000..a0f7250 --- /dev/null +++ b/tests/models/Appointment.test.ts @@ -0,0 +1,116 @@ +import { describe, expect, test, beforeEach } from "bun:test"; +import { Appointment } from "@models/Appointment"; +import { JsonApiSerializer } from '../../src/utils/JsonSerializer'; +import { Hydrator } from '../../src/utils/Hydrator'; + +describe('Appointment Model', () => { + const newId = 'b9cbbac7-65aa-40d1-aed8-f68b71aa6b6e'; + const newAppointmentData = { + start_time: '2023-10-15T10:00:00Z', + end_time: '2023-10-15T11:00:00Z', + notes: 'Annual maintenance check', + customer_interaction: 'c68a5f32-b9a1-4dab-8f11-7ec2f6b89001', + operation: 'd92c4f18-7a32-4e7c-9f26-8c03e1bca7e3' + }; + + let newAppointment; + let serializer; + + beforeEach(() => { + newAppointment = new Appointment(newAppointmentData); + const hydrator = new Hydrator(); + serializer = new JsonApiSerializer(hydrator.getModelMap()); + }); + + const verifyPayloadStructure = (payload, includeId = false) => { + const expectedPayload = { + data: { + type: "appointments", + attributes: { + start_time: newAppointmentData.start_time, + end_time: newAppointmentData.end_time, + notes: newAppointmentData.notes + }, + relationships: {} + } + }; + + if (includeId) { + expectedPayload.data['id'] = newId; + } + + expect(payload).toEqual(expectedPayload); + }; + + test('newly created model should have correct attributes', () => { + expect(newAppointment.type).toBe('appointments'); + expect(newAppointment.start_time).toBe(newAppointmentData.start_time); + expect(newAppointment.end_time).toBe(newAppointmentData.end_time); + expect(newAppointment.notes).toBe(newAppointmentData.notes); + }); + + test('create payload should have correct attributes and relationships', () => { + const payload = serializer.buildCreatePayload(newAppointment); + verifyPayloadStructure(payload); + }); + + test('patch payload should have correct attributes and relationships', () => { + newAppointment.id = newId; + const payload = serializer.buildUpdatePayload(newAppointment); + verifyPayloadStructure(payload, true); + }); + + test('should handle input data with attributes structure', () => { + const dataWithAttributes = { + attributes: { + start_time: '2023-11-20T14:00:00Z', + end_time: '2023-11-20T15:00:00Z', + notes: 'Follow-up appointment' + }, + relationships: { + customer_interaction: { data: { id: 'e12f3456-7890-abcd-ef12-123456789012', type: 'customer-interactions' } }, + operation: { data: { id: 'f23e4567-8901-bcde-f123-234567890123', type: 'operations' } } + } + }; + + const appointment = new Appointment(dataWithAttributes); + + expect(appointment.start_time).toBe(dataWithAttributes.attributes.start_time); + expect(appointment.end_time).toBe(dataWithAttributes.attributes.end_time); + expect(appointment.notes).toBe(dataWithAttributes.attributes.notes); + }); + + test('should handle missing or empty data', () => { + const emptyAppointment = new Appointment(); + + expect(emptyAppointment.type).toBe('appointments'); + expect(emptyAppointment.start_time).toBe(''); + expect(emptyAppointment.end_time).toBe(''); + expect(emptyAppointment.notes).toBe(''); + }); + + test('jsonApiMapping should return correct mapping structure', () => { + const mapping = newAppointment.jsonApiMapping(); + + expect(mapping.attributes).toContain('start_time'); + expect(mapping.attributes).toContain('end_time'); + expect(mapping.attributes).toContain('notes'); + expect(mapping.relationships.customer_interaction).toBe('customer-interactions'); + expect(mapping.relationships.operation).toBe('operations'); + }); + + test('should have correct static relationship definitions', () => { + expect(Appointment.relationships).toEqual([ + { + name: 'customer_interaction', + type: 'single', + modelType: 'customer-interactions', + }, + { + name: 'operation', + type: 'single', + modelType: 'operations', + }, + ]); + }); +}); \ No newline at end of file diff --git a/tests/models/BaseModel.test.ts b/tests/models/BaseModel.test.ts new file mode 100644 index 0000000..d551532 --- /dev/null +++ b/tests/models/BaseModel.test.ts @@ -0,0 +1,159 @@ +import { describe, expect, test } from "bun:test"; +import { BaseModel } from "@models/BaseModel"; + +// Create a concrete implementation of BaseModel for testing +class TestModel extends BaseModel { + public name: string = ''; + public description: string = ''; + public emptyObject: Record = {}; + + constructor(data?: any) { + super(data); + this.type = data?.type ?? 'test-models'; // Set default only if not provided + this.name = data?.name ?? ''; + this.description = data?.description ?? ''; + } +} + +describe('BaseModel', () => { + test('should correctly initialize with id and type', () => { + const data = { + id: 'test-id-123', + type: 'custom-type' + }; + + const model = new TestModel(data); + + expect(model.id).toBe(data.id); + expect(model.type).toBe(data.type); + }); + + test('should set meta, links, and included if provided', () => { + const data = { + id: 'test-id-123', + meta: { createdAt: '2023-01-01', updatedAt: '2023-01-02' }, + links: { self: 'https://api.example.com/test/123' }, + included: { related: { id: 'related-id', type: 'related-type' } } + }; + + const model = new TestModel(data); + + expect(model.meta).toEqual(data.meta); + expect(model.links).toEqual(data.links); + expect(model.included).toEqual(data.included); + }); + + test('should set _relationships if provided', () => { + const data = { + id: 'test-id-123', + relationships: { + parent: { data: { id: 'parent-id', type: 'parent-type' } }, + children: { data: [{ id: 'child-id', type: 'child-type' }] } + } + }; + + const model = new TestModel(data); + + expect(model._relationships).toEqual(data.relationships); + }); + + test('should not set optional properties if empty', () => { + const data = { + id: 'test-id-123', + meta: {}, + links: {}, + included: {}, + relationships: {} + }; + + const model = new TestModel(data); + + expect(model.meta).toBeUndefined(); + expect(model.links).toBeUndefined(); + expect(model.included).toBeUndefined(); + expect(model._relationships).toBeUndefined(); + }); + + test('should initialize with default values when no data is provided', () => { + const model = new TestModel(); + + expect(model.id).toBe(''); + expect(model.type).toBe('test-models'); + expect(model.meta).toBeUndefined(); + expect(model.links).toBeUndefined(); + expect(model.included).toBeUndefined(); + expect(model._relationships).toBeUndefined(); + }); + + test('toJSON should return correct serialized object', () => { + const data = { + id: 'test-id-123', + type: 'test-models', + meta: { createdAt: '2023-01-01' }, + links: { self: 'https://api.example.com/test/123' }, + name: 'Test Name', + description: 'Test Description' + }; + + const model = new TestModel(data); + const json = model.toJSON(); + + expect(json).toEqual({ + id: data.id, + type: data.type, + meta: data.meta, + links: data.links, + name: data.name, + description: data.description + }); + }); + + test('toJSON should exclude empty objects', () => { + const model = new TestModel({ + id: 'test-id-123', + name: 'Test Name' + }); + + const json = model.toJSON(); + + expect(json).toEqual({ + id: 'test-id-123', + type: 'test-models', + name: 'Test Name', + description: '' // Empty string is still included + // emptyObject is excluded because it's an empty object + }); + + // Verify the property exists in the model but is excluded from JSON + expect(model.emptyObject).toBeDefined(); + expect(json.emptyObject).toBeUndefined(); + }); + + test('toJSON should exclude null and undefined values', () => { + const model = new TestModel({ + id: 'test-id-123', + name: 'Test Name' + }); + + // Add null and undefined properties + model['nullProp'] = null; + model['undefinedProp'] = undefined; + + const json = model.toJSON(); + + expect(json.nullProp).toBeUndefined(); + expect(json.undefinedProp).toBeUndefined(); + }); + + test('toJSON should include non-empty arrays', () => { + const model = new TestModel({ + id: 'test-id-123' + }); + + model['items'] = ['item1', 'item2']; + + const json = model.toJSON(); + + expect(json.items).toEqual(['item1', 'item2']); + }); +}); \ No newline at end of file diff --git a/tests/models/Contact.test.ts b/tests/models/Contact.test.ts new file mode 100644 index 0000000..34eb111 --- /dev/null +++ b/tests/models/Contact.test.ts @@ -0,0 +1,127 @@ +import { describe, expect, test, beforeEach } from "bun:test"; +import { Contact } from "@models/Contact"; +import { JsonApiSerializer } from '../../src/utils/JsonSerializer'; +import { Hydrator } from '../../src/utils/Hydrator'; + +describe('Contact Model', () => { + const newId = 'a8cbbac7-65aa-40d1-aed8-f68b71aa6b6e'; + const newContactData = { + salutation: 'Mr', + first_name: 'John', + last_name: 'Smith', + telephone: '07700900123', + email: 'john.smith@example.com' + }; + + let newContact; + let serializer; + + beforeEach(() => { + newContact = new Contact(newContactData); + const hydrator = new Hydrator(); + serializer = new JsonApiSerializer(hydrator.getModelMap()); + }); + + const verifyPayloadStructure = (payload, includeId = false) => { + const expectedPayload = { + data: { + type: "contacts", + attributes: { + salutation: newContactData.salutation, + first_name: newContactData.first_name, + last_name: newContactData.last_name, + telephone: newContactData.telephone, + email: newContactData.email + }, + relationships: {} + } + }; + + if (includeId) { + expectedPayload.data['id'] = newId; + } + + expect(payload).toEqual(expectedPayload); + }; + + test('newly created model should have correct attributes', () => { + expect(newContact.type).toBe('contacts'); + expect(newContact.salutation).toBe(newContactData.salutation); + expect(newContact.first_name).toBe(newContactData.first_name); + expect(newContact.last_name).toBe(newContactData.last_name); + expect(newContact.telephone).toBe(newContactData.telephone); + expect(newContact.email).toBe(newContactData.email); + }); + + test('create payload should have correct attributes', () => { + const payload = serializer.buildCreatePayload(newContact); + verifyPayloadStructure(payload); + }); + + test('patch payload should have correct attributes', () => { + newContact.id = newId; + const payload = serializer.buildUpdatePayload(newContact); + verifyPayloadStructure(payload, true); + }); + + test('should handle input data with attributes structure', () => { + const dataWithAttributes = { + attributes: { + salutation: 'Ms', + first_name: 'Jane', + last_name: 'Doe', + telephone: '07700900456', + email: 'jane.doe@example.com' + } + }; + + const contact = new Contact(dataWithAttributes); + + expect(contact.salutation).toBe(dataWithAttributes.attributes.salutation); + expect(contact.first_name).toBe(dataWithAttributes.attributes.first_name); + expect(contact.last_name).toBe(dataWithAttributes.attributes.last_name); + expect(contact.telephone).toBe(dataWithAttributes.attributes.telephone); + expect(contact.email).toBe(dataWithAttributes.attributes.email); + }); + + test('should handle missing or empty data', () => { + const emptyContact = new Contact(); + + expect(emptyContact.type).toBe('contacts'); + expect(emptyContact.salutation).toBe(''); + expect(emptyContact.first_name).toBe(''); + expect(emptyContact.last_name).toBe(''); + expect(emptyContact.telephone).toBe(''); + expect(emptyContact.email).toBe(''); + }); + + test('jsonApiMapping should return correct mapping structure', () => { + const mapping = newContact.jsonApiMapping(); + + expect(mapping.attributes).toContain('salutation'); + expect(mapping.attributes).toContain('first_name'); + expect(mapping.attributes).toContain('last_name'); + expect(mapping.attributes).toContain('telephone'); + expect(mapping.attributes).toContain('email'); + }); + + test('should have correct static relationship definitions', () => { + expect(Contact.relationships).toEqual([ + { + name: 'customer_accounts', + type: 'array', + modelType: 'customer-accounts', + }, + { + name: 'representative', + type: 'array', + modelType: 'customer-interactions', + }, + { + name: 'properties', + type: 'array', + modelType: 'properties', + }, + ]); + }); +}); \ No newline at end of file diff --git a/tests/models/CustomerInteraction.test.ts b/tests/models/CustomerInteraction.test.ts new file mode 100644 index 0000000..019363d --- /dev/null +++ b/tests/models/CustomerInteraction.test.ts @@ -0,0 +1,104 @@ +import { describe, expect, test, beforeEach } from "bun:test"; +import { CustomerInteraction } from "@models/CustomerInteraction"; +import { JsonApiSerializer } from '../../src/utils/JsonSerializer'; +import { Hydrator } from '../../src/utils/Hydrator'; + +describe('CustomerInteraction Model', () => { + const newId = 'a8cbbac7-65aa-40d1-aed8-f68b71aa6b6e'; + const newInteractionData = { + method: 'email', + direction: 'outbound', + date_time: '2023-01-01T12:00:00Z', + contacted: true, + status: 'completed', + notes: 'Customer was informed about upcoming maintenance' + }; + + let newInteraction; + let serializer; + + beforeEach(() => { + newInteraction = new CustomerInteraction(newInteractionData); + const hydrator = new Hydrator(); + serializer = new JsonApiSerializer(hydrator.getModelMap()); + }); + + const verifyPayloadStructure = (payload, includeId = false) => { + const expectedPayload = { + data: { + type: "customer-interactions", + attributes: { + method: newInteractionData.method, + direction: newInteractionData.direction, + date_time: newInteractionData.date_time, + contacted: newInteractionData.contacted, + status: newInteractionData.status, + notes: newInteractionData.notes + }, + relationships: {} + } + }; + + if (includeId) { + expectedPayload.data['id'] = newId; + } + + expect(payload).toEqual(expectedPayload); + }; + + test('newly created model should have correct attributes', () => { + expect(newInteraction.type).toBe('customer-interactions'); + expect(newInteraction.method).toBe(newInteractionData.method); + expect(newInteraction.direction).toBe(newInteractionData.direction); + expect(newInteraction.date_time).toBe(newInteractionData.date_time); + expect(newInteraction.contacted).toBe(newInteractionData.contacted); + expect(newInteraction.status).toBe(newInteractionData.status); + expect(newInteraction.notes).toBe(newInteractionData.notes); + }); + + test('create payload should have correct attributes', () => { + const payload = serializer.buildCreatePayload(newInteraction); + verifyPayloadStructure(payload); + }); + + test('patch payload should have correct attributes', () => { + newInteraction.id = newId; + const payload = serializer.buildUpdatePayload(newInteraction); + verifyPayloadStructure(payload, true); + }); + + test('should handle input data with attributes structure', () => { + const dataWithAttributes = { + attributes: { + method: 'telephone', + direction: 'inbound', + date_time: '2023-02-15T14:30:00Z', + contacted: true, + status: 'in-progress', + notes: 'Customer called with questions about billing' + } + }; + + const interaction = new CustomerInteraction(dataWithAttributes); + + expect(interaction.method).toBe(dataWithAttributes.attributes.method); + expect(interaction.direction).toBe(dataWithAttributes.attributes.direction); + expect(interaction.date_time).toBe(dataWithAttributes.attributes.date_time); + expect(interaction.contacted).toBe(dataWithAttributes.attributes.contacted); + expect(interaction.status).toBe(dataWithAttributes.attributes.status); + expect(interaction.notes).toBe(dataWithAttributes.attributes.notes); + }); + + test('should handle missing or empty data', () => { + const emptyInteraction = new CustomerInteraction(); + + expect(emptyInteraction.type).toBe('customer-interactions'); + expect(emptyInteraction.method).toBe(''); + expect(emptyInteraction.direction).toBe(''); + expect(emptyInteraction.date_time).toBe(''); + expect(emptyInteraction.contacted).toBe(false); + expect(emptyInteraction.status).toBe(''); + expect(emptyInteraction.notes).toBe(''); + }); + +}); \ No newline at end of file diff --git a/tests/models/EquipmentModel.test.ts b/tests/models/EquipmentModel.test.ts new file mode 100644 index 0000000..7f7444a --- /dev/null +++ b/tests/models/EquipmentModel.test.ts @@ -0,0 +1,134 @@ +import { describe, expect, test, beforeEach } from "bun:test"; +import { EquipmentModel } from "@models/EquipmentModel"; +import { JsonApiSerializer } from '../../src/utils/JsonSerializer'; +import { Hydrator } from '../../src/utils/Hydrator'; + +describe('EquipmentModel Model', () => { + const newId = 'b9cbbac7-65aa-40d1-aed8-f68b71aa6b6e'; + const manufacturerId = 'c68a5f32-b9a1-4dab-8f11-7ec2f6b89001'; + const categoryId1 = 'd92c4f18-7a32-4e7c-9f26-8c03e1bca7e3'; + const categoryId2 = 'e76f5989-9b12-4567-8f11-7ec2f6b89002'; + + const newEquipmentModelData = { + name: 'Power Drill XJ-5000', + description: 'Industrial-grade power drill', + specification: { + vibration: { + magnitude: 2.5 + } + }, + documentation: [ + { + name: 'User Manual', + description: 'Operating instructions', + link: 'https://example.com/manual' + } + ] + }; + + const includedData = [ + { + id: categoryId1, + type: 'equipment-categories', + attributes: { + name: 'Power Tools' + } + }, + { + id: categoryId2, + type: 'equipment-categories', + attributes: { + name: 'Drills' + } + } + ]; + + let newEquipmentModel; + let serializer; + + beforeEach(() => { + newEquipmentModel = new EquipmentModel({ + attributes: { + name: newEquipmentModelData.name, + description: newEquipmentModelData.description, + specification: newEquipmentModelData.specification, + documentation: newEquipmentModelData.documentation + }, + relationships: { + manufacturer: { + id: manufacturerId, + type: 'equipment-manufacturers' + }, + categories: { + data: [ + { id: categoryId1, type: 'equipment-categories' }, + { id: categoryId2, type: 'equipment-categories' } + ] + } + }, + included: includedData + }); + const hydrator = new Hydrator(); + serializer = new JsonApiSerializer(hydrator.getModelMap()); + }); + + test('newly created model should have correct attributes', () => { + expect(newEquipmentModel.type).toBe('equipment-models'); + expect(newEquipmentModel.name).toBe(newEquipmentModelData.name); + expect(newEquipmentModel.description).toBe(newEquipmentModelData.description); + expect(newEquipmentModel.specification).toEqual(newEquipmentModelData.specification); + expect(newEquipmentModel.documentation).toEqual(newEquipmentModelData.documentation); + + // Check categories hydration from included data + expect(newEquipmentModel.categories).toHaveLength(2); + expect(newEquipmentModel.categories[0].id).toBe(categoryId1); + expect(newEquipmentModel.categories[0].name).toBe('Power Tools'); + expect(newEquipmentModel.categories[1].id).toBe(categoryId2); + expect(newEquipmentModel.categories[1].name).toBe('Drills'); + }); + + test('should handle missing or empty data', () => { + const emptyEquipmentModel = new EquipmentModel(); + + expect(emptyEquipmentModel.type).toBe('equipment-models'); + expect(emptyEquipmentModel.name).toBe(''); + expect(emptyEquipmentModel.description).toBe(''); + expect(emptyEquipmentModel.documentation).toEqual([]); + expect(emptyEquipmentModel.categories).toEqual([]); + expect(emptyEquipmentModel.specification).toEqual({}); + }); + + test('should handle category data without included data', () => { + const modelWithoutIncluded = new EquipmentModel({ + relationships: { + categories: { + data: [ + { id: categoryId1, type: 'equipment-categories' }, + { id: categoryId2, type: 'equipment-categories' } + ] + } + } + }); + + expect(modelWithoutIncluded.categories).toHaveLength(2); + expect(modelWithoutIncluded.categories[0].id).toBe(categoryId1); + expect(modelWithoutIncluded.categories[0].name).toBe(''); + expect(modelWithoutIncluded.categories[1].id).toBe(categoryId2); + expect(modelWithoutIncluded.categories[1].name).toBe(''); + }); + + test('should have correct static relationship definitions', () => { + expect(EquipmentModel.relationships).toEqual([ + { + name: 'manufacturer', + type: 'single', + modelType: 'equipment-manufacturers' + }, + { + name: 'categories', + type: 'array', + modelType: 'equipment-categories' + } + ]); + }); +}); \ No newline at end of file diff --git a/tests/models/FormVersion.test.ts b/tests/models/FormVersion.test.ts new file mode 100644 index 0000000..bd53ce9 --- /dev/null +++ b/tests/models/FormVersion.test.ts @@ -0,0 +1,67 @@ +import { describe, expect, test, beforeEach } from "bun:test"; +import { FormVersion } from "@models/FormVersion"; +import { JsonApiSerializer } from '../../src/utils/JsonSerializer'; +import { Hydrator } from '../../src/utils/Hydrator'; + +describe('FormVersion Model', () => { + const newId = 'h6i7j8k9-l0m1-n2o3-p4q5-r6s7t8u9v0w1'; + const newFormVersionData = { + attributes: { + name: 'Safety Inspection Form v2.1' + } + }; + + let newFormVersion; + let serializer; + + beforeEach(() => { + newFormVersion = new FormVersion(newFormVersionData); + const hydrator = new Hydrator(); + serializer = new JsonApiSerializer(hydrator.getModelMap()); + }); + + const verifyPayloadStructure = (payload, includeId = false) => { + const expectedPayload = { + data: { + type: "form-version", + attributes: { + name: newFormVersionData.attributes.name + }, + } + }; + + if (includeId) { + expectedPayload.data['id'] = newId; + } + + expect(payload).toEqual(expectedPayload); + }; + + test('newly created model should have correct attributes', () => { + expect(newFormVersion.type).toBe('form-version'); + expect(newFormVersion.name).toBe(newFormVersionData.attributes.name); + }); + + test('create payload should have correct attributes and relationships', () => { + const payload = serializer.buildCreatePayload(newFormVersion); + verifyPayloadStructure(payload); + }); + + test('should handle input data with attributes structure', () => { + const dataWithAttributes = { + attributes: { + name: 'Risk Assessment Form v1.3' + } + }; + + const formVersion = new FormVersion(dataWithAttributes); + expect(formVersion.name).toBe(dataWithAttributes.attributes.name); + }); + + test('should handle missing or empty data', () => { + const emptyFormVersion = new FormVersion(); + + expect(emptyFormVersion.type).toBe('form-version'); + expect(emptyFormVersion.name).toBe(''); + }); +}); \ No newline at end of file diff --git a/tests/models/GroupService.test.ts b/tests/models/GroupService.test.ts new file mode 100644 index 0000000..901900b --- /dev/null +++ b/tests/models/GroupService.test.ts @@ -0,0 +1,118 @@ +import { describe, expect, test, beforeEach } from "bun:test"; +import { Group } from "@models/Group"; +import { JsonApiSerializer } from '../../src/utils/JsonSerializer'; +import { Hydrator } from '../../src/utils/Hydrator'; + +describe('Group Model', () => { + const newId = 'b9cbbac7-65aa-40d1-aed8-f68b71aa6b6e'; + const newGroupData = { + name: 'Maintenance Team', + description: 'Team responsible for equipment maintenance', + bindings: [ + { + id: 'role-1', + role: 'technician', + condition: { + gate: 'AND', + rules: [ + { + type: 'property', + operator: 'equals', + key: 'department', + value: 'maintenance' + } + ] + } + } + ] + }; + + let newGroup; + let serializer; + + beforeEach(() => { + newGroup = new Group(newGroupData); + const hydrator = new Hydrator(); + serializer = new JsonApiSerializer(hydrator.getModelMap()); + }); + + const verifyPayloadStructure = (payload, includeId = false) => { + const expectedPayload = { + data: { + type: "groups", + attributes: { + name: newGroupData.name, + description: newGroupData.description, + bindings: newGroupData.bindings + }, + relationships: {} + } + }; + + if (includeId) { + expectedPayload.data['id'] = newId; + } + + expect(payload).toEqual(expectedPayload); + }; + + test('newly created model should have correct attributes', () => { + expect(newGroup.type).toBe('groups'); + expect(newGroup.name).toBe(newGroupData.name); + expect(newGroup.description).toBe(newGroupData.description); + expect(newGroup.bindings).toEqual(newGroupData.bindings); + }); + + test('create payload should have correct attributes', () => { + const payload = serializer.buildCreatePayload(newGroup); + verifyPayloadStructure(payload); + }); + + test('patch payload should have correct attributes', () => { + newGroup.id = newId; + const payload = serializer.buildUpdatePayload(newGroup); + verifyPayloadStructure(payload, true); + }); + + test('should handle input data with attributes structure', () => { + const dataWithAttributes = { + attributes: { + name: 'Field Engineers', + description: 'Engineers working on-site', + bindings: [ + { + id: 'role-2', + role: 'engineer', + condition: { + gate: 'OR', + rules: [ + { + type: 'property', + operator: 'equals', + key: 'location', + value: 'field' + } + ] + } + } + ] + } + }; + + const group = new Group(dataWithAttributes); + + expect(group.name).toBe(dataWithAttributes.attributes.name); + expect(group.description).toBe(dataWithAttributes.attributes.description); + expect(group.bindings).toEqual(dataWithAttributes.attributes.bindings); + }); + + test('should handle missing or empty data', () => { + const emptyGroup = new Group(); + + expect(emptyGroup.type).toBe('groups'); + expect(emptyGroup.name).toBe(''); + expect(emptyGroup.description).toBe(''); + expect(emptyGroup.bindings).toEqual([]); + }); + +}); \ No newline at end of file diff --git a/tests/models/Log.test.ts b/tests/models/Log.test.ts new file mode 100644 index 0000000..a894950 --- /dev/null +++ b/tests/models/Log.test.ts @@ -0,0 +1,117 @@ +import { describe, expect, test, beforeEach } from "bun:test"; +import { Log } from "@models/Log"; +import { JsonApiSerializer } from '../../src/utils/JsonSerializer'; +import { Hydrator } from '../../src/utils/Hydrator'; + +describe('Log Model', () => { + const newId = 'e3f9a0b1-45c7-6d8e-af5g-0h1i2j3k4l5m'; + const newLogData = { + attributes: { + actor: { + type: 'user', + id: 'user-123' + }, + duration: 154, + request: { + time: '2023-10-15T10:00:00Z', + headers: { 'content-type': ['application/json'] }, + body: '{"data": "example"}', + path: '/api/v1/resource', + query: { 'filter': ['status=active'] }, + raw_query: 'filter=status=active', + method: 'GET', + content_length: 123 + }, + response: { + time: '2023-10-15T10:00:01Z', + body: '{"success": true}', + headers: { 'content-type': ['application/json'] }, + status: 200 + } + } + }; + + let newLog; + let serializer; + + beforeEach(() => { + newLog = new Log(newLogData); + const hydrator = new Hydrator(); + serializer = new JsonApiSerializer(hydrator.getModelMap()); + }); + + const verifyPayloadStructure = (payload, includeId = false) => { + const expectedPayload = { + data: { + type: "logs", + attributes: { + actor: newLogData.attributes.actor, + duration: newLogData.attributes.duration, + request: newLogData.attributes.request, + response: newLogData.attributes.response + }, + } + }; + + if (includeId) { + expectedPayload.data['id'] = newId; + } + + expect(payload).toEqual(expectedPayload); + }; + + test('newly created model should have correct attributes', () => { + expect(newLog.type).toBe('logs'); + expect(newLog.actor).toEqual(newLogData.attributes.actor); + expect(newLog.duration).toBe(newLogData.attributes.duration); + expect(newLog.request).toEqual(newLogData.attributes.request); + expect(newLog.response).toEqual(newLogData.attributes.response); + }); + + test('create payload should have correct attributes and relationships', () => { + const payload = serializer.buildCreatePayload(newLog); + verifyPayloadStructure(payload); + }); + + test('should handle input data with attributes structure', () => { + const dataWithAttributes = { + attributes: { + actor: { type: 'service', id: 'service-456' }, + duration: 250, + request: { + time: '2023-11-20T14:00:00Z', + headers: { 'authorization': ['Bearer token'] }, + body: '{"action": "update"}', + path: '/api/v1/resource/123', + query: {}, + raw_query: '', + method: 'POST', + content_length: 45 + }, + response: { + time: '2023-11-20T14:00:01Z', + body: '{"status": "updated"}', + headers: { 'content-type': ['application/json'] }, + status: 201 + } + } + }; + + const log = new Log(dataWithAttributes); + + expect(log.actor).toEqual(dataWithAttributes.attributes.actor); + expect(log.duration).toBe(dataWithAttributes.attributes.duration); + expect(log.request).toEqual(dataWithAttributes.attributes.request); + expect(log.response).toEqual(dataWithAttributes.attributes.response); + }); + + test('should handle missing or empty data', () => { + const emptyLog = new Log(); + + expect(emptyLog.type).toBe('logs'); + expect(emptyLog.actor).toEqual({ type: '', id: '' }); + expect(emptyLog.duration).toBe(0); + expect(emptyLog.request.method).toBe(''); + expect(emptyLog.response.status).toBe(0); + }); +}); \ No newline at end of file diff --git a/tests/models/MotRecord.test.ts b/tests/models/MotRecord.test.ts new file mode 100644 index 0000000..9f1f923 --- /dev/null +++ b/tests/models/MotRecord.test.ts @@ -0,0 +1,144 @@ +import { describe, expect, test, beforeEach } from "bun:test"; +import { MotRecord } from "@models/MotRecord"; +import { JsonApiSerializer } from '../../src/utils/JsonSerializer'; +import { Hydrator } from '../../src/utils/Hydrator'; + +describe('MotRecord Model', () => { + const newId = 'b9cbbac7-65aa-40d1-aed8-f68b71aa6b6e'; + const newMotRecordData = { + completedDate: '2023-10-15T10:00:00Z', + dataSource: 'dvla', + defects: [ + { + dangerous: true, + text: 'Brake fluid level below minimum', + type: 'major' + }, + { + dangerous: false, + text: 'Windscreen wiper damaged', + type: 'minor' + } + ], + expiryDate: '2024-10-15T10:00:00Z', + odometer: { + type: 'read', + unit: 'miles', + value: 45000 + }, + result: 'pass' + }; + + let newMotRecord; + let serializer; + + beforeEach(() => { + // Pass data properly to match the constructor's expected format + newMotRecord = new MotRecord({ + attributes: { + completed_date: newMotRecordData.completedDate, + data_source: newMotRecordData.dataSource, + defects: newMotRecordData.defects, + expiry_date: newMotRecordData.expiryDate, + odometer: newMotRecordData.odometer, + result: newMotRecordData.result + } + }); + const hydrator = new Hydrator(); + serializer = new JsonApiSerializer(hydrator.getModelMap()); + }); + + const verifyPayloadStructure = (payload, includeId = false) => { + const expectedPayload = { + data: { + type: "vehicle-mot-records", + attributes: {}, + relationships: {} + } + }; + + if (includeId) { + expectedPayload.data['id'] = newId; + } + + expect(payload).toEqual(expectedPayload); + }; + + test('newly created model should have correct attributes', () => { + expect(newMotRecord.type).toBe('vehicle-mot-records'); + expect(newMotRecord.completedDate).toBe(newMotRecordData.completedDate); + expect(newMotRecord.dataSource).toBe(newMotRecordData.dataSource); + expect(newMotRecord.defects).toEqual(newMotRecordData.defects); + expect(newMotRecord.expiryDate).toBe(newMotRecordData.expiryDate); + expect(newMotRecord.odometer).toEqual(newMotRecordData.odometer); + expect(newMotRecord.result).toBe(newMotRecordData.result); + }); + + test('create payload should have correct attributes and relationships', () => { + const payload = serializer.buildCreatePayload(newMotRecord); + verifyPayloadStructure(payload); + }); + + test('patch payload should have correct attributes and relationships', () => { + newMotRecord.id = newId; + const payload = serializer.buildUpdatePayload(newMotRecord); + verifyPayloadStructure(payload, true); + }); + + test('should handle input data with attributes structure', () => { + const dataWithAttributes = { + attributes: { + completed_date: '2023-11-20T14:00:00Z', + data_source: 'mot', + defects: [ + { + dangerous: false, + text: 'Headlight alignment incorrect', + type: 'advisory' + } + ], + expiry_date: '2024-11-20T14:00:00Z', + odometer: { + type: 'read', + unit: 'kilometers', + value: 72500 + }, + result: 'pass' + } + }; + + const motRecord = new MotRecord(dataWithAttributes); + + expect(motRecord.completedDate).toBe(dataWithAttributes.attributes.completed_date); + expect(motRecord.dataSource).toBe(dataWithAttributes.attributes.data_source); + expect(motRecord.defects).toEqual(dataWithAttributes.attributes.defects); + expect(motRecord.expiryDate).toBe(dataWithAttributes.attributes.expiry_date); + expect(motRecord.odometer).toEqual(dataWithAttributes.attributes.odometer); + expect(motRecord.result).toBe(dataWithAttributes.attributes.result); + }); + + test('should handle missing or empty data', () => { + const emptyMotRecord = new MotRecord(); + + expect(emptyMotRecord.type).toBe('vehicle-mot-records'); + expect(emptyMotRecord.completedDate).toBe(''); + expect(emptyMotRecord.dataSource).toBe(''); + expect(emptyMotRecord.defects).toEqual([]); + expect(emptyMotRecord.expiryDate).toBe(''); + expect(emptyMotRecord.odometer).toEqual({ + type: 'read', + unit: 'kilometers', + value: 0 + }); + expect(emptyMotRecord.result).toBe(''); + }); + + test('jsonApiMapping should return correct mapping structure', () => { + const mapping = newMotRecord.jsonApiMapping(); + expect(mapping).toEqual({}); + }); + + test('should have correct static relationship definitions', () => { + expect(MotRecord.relationships).toEqual([]); + }); +}); \ No newline at end of file diff --git a/tests/models/Operation.test.ts b/tests/models/Operation.test.ts new file mode 100644 index 0000000..eb5a506 --- /dev/null +++ b/tests/models/Operation.test.ts @@ -0,0 +1,140 @@ +import { describe, expect, test, beforeEach } from "bun:test"; +import { Operation } from "@models/Operation"; +import { JsonApiSerializer } from '../../src/utils/JsonSerializer'; +import { Hydrator } from '../../src/utils/Hydrator'; + +describe('Operation Model', () => { + const newId = 'd9cbbac7-65aa-40d1-aed8-f68b71aa6b6e'; + const newOperationData = { + name: 'Field Maintenance Operation', + code: 'FM-2023-001', + description: 'Scheduled field maintenance for Q4 2023', + start_date: '2023-10-01T00:00:00Z', + end_date: '2023-12-31T23:59:59Z', + labels: [ + { key: 'priority', value: 'high' }, + { key: 'region', value: 'north' } + ], + uprns: ['1234567890', '9876543210'], + usrns: ['USRN001', 'USRN002'], + completed: false, + aborted: false, + cancelled: false + }; + + let newOperation; + let serializer; + + beforeEach(() => { + newOperation = new Operation(newOperationData); + const hydrator = new Hydrator(); + serializer = new JsonApiSerializer(hydrator.getModelMap()); + }); + + const verifyPayloadStructure = (payload, includeId = false) => { + const expectedPayload = { + data: { + type: "operations", + attributes: { + name: newOperationData.name, + code: newOperationData.code, + description: newOperationData.description, + start_date: newOperationData.start_date, + end_date: newOperationData.end_date, + labels: newOperationData.labels, + uprns: newOperationData.uprns, + usrns: newOperationData.usrns, + completed: newOperationData.completed, + aborted: newOperationData.aborted, + cancelled: newOperationData.cancelled + }, + relationships: {} + } + }; + + if (includeId) { + expectedPayload.data['id'] = newId; + } + + expect(payload).toEqual(expectedPayload); + }; + + test('newly created model should have correct attributes', () => { + expect(newOperation.type).toBe('operations'); + expect(newOperation.name).toBe(newOperationData.name); + expect(newOperation.code).toBe(newOperationData.code); + expect(newOperation.description).toBe(newOperationData.description); + expect(newOperation.start_date).toBe(newOperationData.start_date); + expect(newOperation.end_date).toBe(newOperationData.end_date); + expect(newOperation.labels).toEqual(newOperationData.labels); + expect(newOperation.uprns).toEqual(newOperationData.uprns); + expect(newOperation.usrns).toEqual(newOperationData.usrns); + expect(newOperation.completed).toBe(newOperationData.completed); + expect(newOperation.aborted).toBe(newOperationData.aborted); + expect(newOperation.cancelled).toBe(newOperationData.cancelled); + }); + + test('create payload should have correct attributes', () => { + const payload = serializer.buildCreatePayload(newOperation); + verifyPayloadStructure(payload); + }); + + test('patch payload should have correct attributes', () => { + newOperation.id = newId; + const payload = serializer.buildUpdatePayload(newOperation); + verifyPayloadStructure(payload, true); + }); + + test('should handle input data with attributes structure', () => { + const dataWithAttributes = { + attributes: { + name: 'Emergency Response', + code: 'ER-2023-005', + description: 'Response to reported infrastructure issue', + start_date: '2023-11-15T08:00:00Z', + end_date: '2023-11-16T18:00:00Z', + labels: [ + { key: 'priority', value: 'urgent' }, + { key: 'type', value: 'emergency' } + ], + uprns: ['5678901234'], + usrns: ['USRN003'], + completed: true, + aborted: false, + cancelled: false + } + }; + + const operation = new Operation(dataWithAttributes); + + expect(operation.name).toBe(dataWithAttributes.attributes.name); + expect(operation.code).toBe(dataWithAttributes.attributes.code); + expect(operation.description).toBe(dataWithAttributes.attributes.description); + expect(operation.start_date).toBe(dataWithAttributes.attributes.start_date); + expect(operation.end_date).toBe(dataWithAttributes.attributes.end_date); + expect(operation.labels).toEqual(dataWithAttributes.attributes.labels); + expect(operation.uprns).toEqual(dataWithAttributes.attributes.uprns); + expect(operation.usrns).toEqual(dataWithAttributes.attributes.usrns); + expect(operation.completed).toBe(dataWithAttributes.attributes.completed); + expect(operation.aborted).toBe(dataWithAttributes.attributes.aborted); + expect(operation.cancelled).toBe(dataWithAttributes.attributes.cancelled); + }); + + test('should handle missing or empty data', () => { + const emptyOperation = new Operation(); + + expect(emptyOperation.type).toBe('operations'); + expect(emptyOperation.name).toBe(''); + expect(emptyOperation.code).toBe(''); + expect(emptyOperation.description).toBe(''); + expect(emptyOperation.start_date).toBe(''); + expect(emptyOperation.end_date).toBe(''); + expect(emptyOperation.labels).toEqual([]); + expect(emptyOperation.uprns).toEqual([]); + expect(emptyOperation.usrns).toEqual([]); + expect(emptyOperation.completed).toBe(false); + expect(emptyOperation.aborted).toBe(false); + expect(emptyOperation.cancelled).toBe(false); + }); + +}); \ No newline at end of file diff --git a/tests/models/OperationTemplate.test.ts b/tests/models/OperationTemplate.test.ts new file mode 100644 index 0000000..1eaaac4 --- /dev/null +++ b/tests/models/OperationTemplate.test.ts @@ -0,0 +1,96 @@ +import { describe, expect, test, beforeEach } from "bun:test"; +import { OperationTemplate } from "@models/OperationTemplate"; +import { JsonApiSerializer } from '../../src/utils/JsonSerializer'; +import { Hydrator } from '../../src/utils/Hydrator'; + +describe('OperationTemplate Model', () => { + const newId = 'c1d78e9f-23a5-4b6c-9d3e-8f67a1b2c3d4'; + const newOperationTemplateData = { + name: 'Standard Maintenance Procedure', + labels: ['maintenance', 'standard'], + requirements: { + forms: [ + { id: 'form-123', required: true }, + { id: 'form-456', required: false } + ] + } + }; + + let newOperationTemplate; + let serializer; + + beforeEach(() => { + newOperationTemplate = new OperationTemplate(newOperationTemplateData); + const hydrator = new Hydrator(); + serializer = new JsonApiSerializer(hydrator.getModelMap()); + }); + + const verifyPayloadStructure = (payload, includeId = false) => { + const expectedPayload = { + data: { + type: "operation-templates", + attributes: { + name: newOperationTemplateData.name, + labels: newOperationTemplateData.labels, + requirements: newOperationTemplateData.requirements + }, + relationships: {} + } + }; + + if (includeId) { + expectedPayload.data['id'] = newId; + } + + expect(payload).toEqual(expectedPayload); + }; + + test('newly created model should have correct attributes', () => { + expect(newOperationTemplate.type).toBe('operation-templates'); + expect(newOperationTemplate.name).toBe(newOperationTemplateData.name); + expect(newOperationTemplate.labels).toEqual(newOperationTemplateData.labels); + expect(newOperationTemplate.requirements).toEqual(newOperationTemplateData.requirements); + }); + + test('create payload should have correct attributes and relationships', () => { + const payload = serializer.buildCreatePayload(newOperationTemplate); + verifyPayloadStructure(payload); + }); + + test('should handle input data with attributes structure', () => { + const dataWithAttributes = { + attributes: { + name: 'Emergency Response Template', + labels: ['emergency', 'urgent'], + requirements: { + forms: [ + { id: 'form-789', required: true } + ] + } + } + }; + + const operationTemplate = new OperationTemplate(dataWithAttributes); + + expect(operationTemplate.name).toBe(dataWithAttributes.attributes.name); + expect(operationTemplate.labels).toEqual(dataWithAttributes.attributes.labels); + expect(operationTemplate.requirements).toEqual(dataWithAttributes.attributes.requirements); + }); + + test('should handle missing or empty data', () => { + const emptyOperationTemplate = new OperationTemplate(); + + expect(emptyOperationTemplate.type).toBe('operation-templates'); + expect(emptyOperationTemplate.name).toBe(''); + expect(emptyOperationTemplate.labels).toEqual([]); + expect(emptyOperationTemplate.requirements).toEqual({ forms: [] }); + }); + + test('jsonApiMapping should return correct mapping structure', () => { + const mapping = newOperationTemplate.jsonApiMapping(); + + expect(mapping.attributes).toContain('name'); + expect(mapping.attributes).toContain('labels'); + expect(mapping.attributes).toContain('requirements'); + }); +}); \ No newline at end of file diff --git a/tests/models/Organisation.test.ts b/tests/models/Organisation.test.ts new file mode 100644 index 0000000..f22ca8e --- /dev/null +++ b/tests/models/Organisation.test.ts @@ -0,0 +1,56 @@ +import { describe, expect, test, beforeEach } from "bun:test"; +import { Organisation } from "@models/Organisation"; +import { JsonApiSerializer } from '../../src/utils/JsonSerializer'; +import { Hydrator } from '../../src/utils/Hydrator'; + +describe('Organisation Model', () => { + const newId = 'd2e89f0a-34b6-5c7d-ae4f-9g78h2i3j4k5'; + const newOrganisationData = {}; + + let newOrganisation; + let serializer; + + beforeEach(() => { + newOrganisation = new Organisation(newOrganisationData); + const hydrator = new Hydrator(); + serializer = new JsonApiSerializer(hydrator.getModelMap()); + }); + + const verifyPayloadStructure = (payload, includeId = false) => { + const expectedPayload = { + data: { + type: "organisations", + attributes: {}, + } + }; + + if (includeId) { + expectedPayload.data['id'] = newId; + } + + expect(payload).toEqual(expectedPayload); + }; + + test('newly created model should have correct type', () => { + expect(newOrganisation.type).toBe('organisations'); + }); + + test('create payload should have correct structure', () => { + const payload = serializer.buildCreatePayload(newOrganisation); + verifyPayloadStructure(payload); + }); + + test('should handle input data with attributes structure', () => { + const dataWithAttributes = { + attributes: {} + }; + + const organisation = new Organisation(dataWithAttributes); + expect(organisation.type).toBe('organisations'); + }); + + test('should handle missing or empty data', () => { + const emptyOrganisation = new Organisation(); + expect(emptyOrganisation.type).toBe('organisations'); + }); +}); \ No newline at end of file diff --git a/tests/models/OrganisationMemberStats.test.ts b/tests/models/OrganisationMemberStats.test.ts new file mode 100644 index 0000000..f7e853a --- /dev/null +++ b/tests/models/OrganisationMemberStats.test.ts @@ -0,0 +1,76 @@ +import { describe, expect, test, beforeEach } from "bun:test"; +import { OrganisationMemberStats } from "@models/OrganisationMemberStats"; +import { JsonApiSerializer } from '../../src/utils/JsonSerializer'; +import { Hydrator } from '../../src/utils/Hydrator'; + +describe('OrganisationMemberStats Model', () => { + const newId = 'f4g5h6i7-89j0-k1l2-m3n4-o5p6q7r8s9t0'; + const newOrganisationMemberStatsData = { + attributes: { + members: { + users: 42, + service_accounts: 15 + } + } + }; + + let newOrganisationMemberStats; + let serializer; + + beforeEach(() => { + newOrganisationMemberStats = new OrganisationMemberStats(newOrganisationMemberStatsData); + const hydrator = new Hydrator(); + serializer = new JsonApiSerializer(hydrator.getModelMap()); + }); + + const verifyPayloadStructure = (payload, includeId = false) => { + const expectedPayload = { + data: { + type: "members-stats", + attributes: { + members: newOrganisationMemberStatsData.attributes.members + }, + } + }; + + if (includeId) { + expectedPayload.data['id'] = newId; + } + + expect(payload).toEqual(expectedPayload); + }; + + test('newly created model should have correct attributes', () => { + expect(newOrganisationMemberStats.type).toBe('members-stats'); + expect(newOrganisationMemberStats.members).toEqual(newOrganisationMemberStatsData.attributes.members); + }); + + test('create payload should have correct attributes and relationships', () => { + const payload = serializer.buildCreatePayload(newOrganisationMemberStats); + verifyPayloadStructure(payload); + }); + + test('should handle input data with attributes structure', () => { + const dataWithAttributes = { + attributes: { + members: { + users: 100, + service_accounts: 25 + } + } + }; + + const stats = new OrganisationMemberStats(dataWithAttributes); + expect(stats.members).toEqual(dataWithAttributes.attributes.members); + }); + + test('should handle missing or empty data', () => { + const emptyStats = new OrganisationMemberStats(); + + expect(emptyStats.type).toBe('members-stats'); + expect(emptyStats.members).toEqual({ + users: 0, + members: 0 // This reflects the current implementation in the model + }); + }); +}); \ No newline at end of file diff --git a/tests/models/Scheme.test.ts b/tests/models/Scheme.test.ts new file mode 100644 index 0000000..448742b --- /dev/null +++ b/tests/models/Scheme.test.ts @@ -0,0 +1,125 @@ +import { describe, expect, test, beforeEach } from "bun:test"; +import { Scheme } from "@models/Scheme"; +import { JsonApiSerializer } from '../../src/utils/JsonSerializer'; +import { Hydrator } from '../../src/utils/Hydrator'; + +describe('Scheme Model', () => { + const newId = 'g5h6i7j8-9k0l-1m2n-3o4p-q5r6s7t8u9v0'; + const newSchemeData = { + attributes: { + name: 'Annual Maintenance Program', + code: 'AMP-2023', + description: 'Comprehensive maintenance program for 2023', + anticipated_start_date: '2023-01-01', + anticipated_end_date: '2023-12-31', + labels: [{ name: 'maintenance', color: '#00FF00' }, { name: 'annual', color: '#0000FF' }] + } + }; + + let newScheme; + let serializer; + + beforeEach(() => { + newScheme = new Scheme(newSchemeData); + const hydrator = new Hydrator(); + serializer = new JsonApiSerializer(hydrator.getModelMap()); + }); + + const verifyPayloadStructure = (payload, includeId = false) => { + const expectedPayload = { + data: { + type: "schemes", + attributes: { + name: newSchemeData.attributes.name, + code: newSchemeData.attributes.code, + description: newSchemeData.attributes.description, + anticipated_start_date: newSchemeData.attributes.anticipated_start_date, + anticipated_end_date: newSchemeData.attributes.anticipated_end_date, + labels: newSchemeData.attributes.labels + }, + relationships: {} + } + }; + + if (includeId) { + expectedPayload.data['id'] = newId; + } + + expect(payload).toEqual(expectedPayload); + }; + + test('newly created model should have correct attributes', () => { + expect(newScheme.type).toBe('schemes'); + expect(newScheme.name).toBe(newSchemeData.attributes.name); + expect(newScheme.code).toBe(newSchemeData.attributes.code); + expect(newScheme.description).toBe(newSchemeData.attributes.description); + expect(newScheme.anticipated_start_date).toBe(newSchemeData.attributes.anticipated_start_date); + expect(newScheme.anticipated_end_date).toBe(newSchemeData.attributes.anticipated_end_date); + expect(newScheme.labels).toEqual(newSchemeData.attributes.labels); + }); + + test('create payload should have correct attributes and relationships', () => { + const payload = serializer.buildCreatePayload(newScheme); + verifyPayloadStructure(payload); + }); + + test('should handle input data with attributes structure', () => { + const dataWithAttributes = { + attributes: { + name: 'Emergency Response Plan', + code: 'ERP-2023', + description: 'Plan for emergency situations', + anticipated_start_date: '2023-06-01', + anticipated_end_date: '2023-12-31', + labels: [{ name: 'emergency', color: '#FF0000' }] + } + }; + + const scheme = new Scheme(dataWithAttributes); + + expect(scheme.name).toBe(dataWithAttributes.attributes.name); + expect(scheme.code).toBe(dataWithAttributes.attributes.code); + expect(scheme.description).toBe(dataWithAttributes.attributes.description); + expect(scheme.anticipated_start_date).toBe(dataWithAttributes.attributes.anticipated_start_date); + expect(scheme.anticipated_end_date).toBe(dataWithAttributes.attributes.anticipated_end_date); + expect(scheme.labels).toEqual(dataWithAttributes.attributes.labels); + }); + + test('should handle missing or empty data', () => { + const emptyScheme = new Scheme(); + + expect(emptyScheme.type).toBe('schemes'); + expect(emptyScheme.name).toBe(''); + expect(emptyScheme.code).toBe(''); + expect(emptyScheme.description).toBe(''); + expect(emptyScheme.anticipated_start_date).toBe(''); + expect(emptyScheme.anticipated_end_date).toBe(''); + expect(emptyScheme.labels).toEqual([]); + }); + + test('jsonApiMapping should return correct mapping structure', () => { + const mapping = newScheme.jsonApiMapping(); + + expect(mapping.attributes).toContain('name'); + expect(mapping.attributes).toContain('code'); + expect(mapping.attributes).toContain('description'); + expect(mapping.attributes).toContain('anticipated_start_date'); + expect(mapping.attributes).toContain('anticipated_end_date'); + expect(mapping.attributes).toContain('labels'); + }); + + test('should have correct static relationship definitions', () => { + expect(Scheme.relationships).toEqual([ + { + name: 'work_orders', + type: 'array', + modelType: 'work-orders', + }, + { + name: 'template', + type: 'single', + modelType: 'scheme-templates', + } + ]); + }); +}); \ No newline at end of file diff --git a/tests/models/SchemeTemplate.test.ts b/tests/models/SchemeTemplate.test.ts new file mode 100644 index 0000000..c6a9e43 --- /dev/null +++ b/tests/models/SchemeTemplate.test.ts @@ -0,0 +1,90 @@ +import { describe, expect, test, beforeEach } from "bun:test"; +import { SchemeTemplate } from "@models/SchemeTemplate"; +import { JsonApiSerializer } from '../../src/utils/JsonSerializer'; +import { Hydrator } from '../../src/utils/Hydrator'; + +describe('SchemeTemplate Model', () => { + const newId = 'b9cbbac7-65aa-40d1-aed8-f68b71aa6b6e'; + const newSchemeTemplateData = { + name: 'Residential Scheme Template', + labels: ['residential', 'standard'] + }; + + let newSchemeTemplate; + let serializer; + + beforeEach(() => { + newSchemeTemplate = new SchemeTemplate(newSchemeTemplateData); + const hydrator = new Hydrator(); + serializer = new JsonApiSerializer(hydrator.getModelMap()); + }); + + const verifyPayloadStructure = (payload, includeId = false) => { + const expectedPayload = { + data: { + type: "scheme-templates", + attributes: { + name: newSchemeTemplateData.name, + labels: newSchemeTemplateData.labels + }, + relationships: {} + } + }; + + if (includeId) { + expectedPayload.data['id'] = newId; + } + + expect(payload).toEqual(expectedPayload); + }; + + test('newly created model should have correct attributes', () => { + expect(newSchemeTemplate.type).toBe('scheme-templates'); + expect(newSchemeTemplate.name).toBe(newSchemeTemplateData.name); + expect(newSchemeTemplate.labels).toEqual(newSchemeTemplateData.labels); + }); + + test('create payload should have correct attributes and relationships', () => { + const payload = serializer.buildCreatePayload(newSchemeTemplate); + verifyPayloadStructure(payload); + }); + + test('patch payload should have correct attributes and relationships', () => { + newSchemeTemplate.id = newId; + const payload = serializer.buildUpdatePayload(newSchemeTemplate); + verifyPayloadStructure(payload, true); + }); + + test('should handle input data with attributes structure', () => { + const dataWithAttributes = { + attributes: { + name: 'Commercial Scheme Template', + labels: ['commercial', 'premium'] + } + }; + + const schemeTemplate = new SchemeTemplate(dataWithAttributes); + + expect(schemeTemplate.name).toBe(dataWithAttributes.attributes.name); + expect(schemeTemplate.labels).toEqual(dataWithAttributes.attributes.labels); + }); + + test('should handle missing or empty data', () => { + const emptySchemeTemplate = new SchemeTemplate(); + + expect(emptySchemeTemplate.type).toBe('scheme-templates'); + expect(emptySchemeTemplate.name).toBe(''); + expect(emptySchemeTemplate.labels).toEqual([]); + }); + + test('jsonApiMapping should return correct mapping structure', () => { + const mapping = newSchemeTemplate.jsonApiMapping(); + + expect(mapping.attributes).toContain('name'); + expect(mapping.attributes).toContain('labels'); + }); + + test('should have correct static relationship definitions', () => { + expect(SchemeTemplate.relationships).toEqual([]); + }); +}); \ No newline at end of file diff --git a/tests/models/ServiceAccount.test.ts b/tests/models/ServiceAccount.test.ts index 516644e..1026666 100644 --- a/tests/models/ServiceAccount.test.ts +++ b/tests/models/ServiceAccount.test.ts @@ -1,5 +1,7 @@ import { describe, it, expect } from "bun:test"; import { ServiceAccount } from "@models/ServiceAccount"; +import { JsonApiSerializer } from '../../src/utils/JsonSerializer'; +import { Hydrator } from '../../src/utils/Hydrator'; describe('ServiceAccount', () => { @@ -43,4 +45,53 @@ describe('ServiceAccount', () => { expect(serviceAccount.meta).toEqual(undefined); }); + it('should return correct jsonApiMapping', () => { + const serviceAccount = new ServiceAccount(); + const mapping = serviceAccount.jsonApiMapping(); + + expect(mapping.attributes).toEqual(['name', 'description']); + }); + + it('should generate correct create and update payloads', () => { + const data = { + id: "f3ca64d7-73b1-4bfc-877a-62f7e1f9e3cb", + attributes: { + name: "Test Account", + description: "This is a service account", + email: "test@example.com", + enabled: true + } + }; + + const serviceAccount = new ServiceAccount(data); + const hydrator = new Hydrator(); + const serializer = new JsonApiSerializer(hydrator.getModelMap()); + + // Test create payload + const createPayload = serializer.buildCreatePayload(serviceAccount); + expect(createPayload).toEqual({ + data: { + type: "service-accounts", + attributes: { + name: "Test Account", + description: "This is a service account" + }, + relationships: {} + } + }); + + // Test update payload + const updatePayload = serializer.buildUpdatePayload(serviceAccount); + expect(updatePayload).toEqual({ + data: { + id: "f3ca64d7-73b1-4bfc-877a-62f7e1f9e3cb", + type: "service-accounts", + attributes: { + name: "Test Account", + description: "This is a service account" + }, + relationships: {} + } + }); + }); }); \ No newline at end of file diff --git a/tests/models/Street.test.ts b/tests/models/Street.test.ts new file mode 100644 index 0000000..f0e67fd --- /dev/null +++ b/tests/models/Street.test.ts @@ -0,0 +1,85 @@ +import { describe, expect, test, beforeEach } from "bun:test"; +import { Street } from "@models/Street"; +import { JsonApiSerializer } from '../../src/utils/JsonSerializer'; +import { Hydrator } from '../../src/utils/Hydrator'; + +describe('Street Model', () => { + const newId = 'i7j8k9l0-m1n2-o3p4-q5r6-s7t8u9v0w1x2'; + const newStreetData = { + attributes: { + usrn: 12345678, + location: { + type: 'Point', + coordinates: [-0.1278, 51.5074] + } + } + }; + + let newStreet; + let serializer; + + beforeEach(() => { + newStreet = new Street(newStreetData); + const hydrator = new Hydrator(); + serializer = new JsonApiSerializer(hydrator.getModelMap()); + }); + + const verifyPayloadStructure = (payload, includeId = false) => { + const expectedPayload = { + data: { + type: "streets", + attributes: { + usrn: newStreetData.attributes.usrn, + location: newStreetData.attributes.location + }, + } + }; + + if (includeId) { + expectedPayload.data['id'] = newId; + } + + expect(payload).toEqual(expectedPayload); + }; + + test('newly created model should have correct attributes', () => { + expect(newStreet.type).toBe('streets'); + expect(newStreet.usrn).toBe(newStreetData.attributes.usrn); + expect(newStreet.location).toEqual(newStreetData.attributes.location); + }); + + test('create payload should have correct attributes and relationships', () => { + const payload = serializer.buildCreatePayload(newStreet); + verifyPayloadStructure(payload); + }); + + test('should handle input data with attributes structure', () => { + const dataWithAttributes = { + attributes: { + usrn: 87654321, + location: { + type: 'LineString', + coordinates: [[-0.1280, 51.5076], [-0.1275, 51.5070]] + } + } + }; + + const street = new Street(dataWithAttributes); + expect(street.usrn).toBe(dataWithAttributes.attributes.usrn); + expect(street.location).toEqual(dataWithAttributes.attributes.location); + }); + + test('should handle data without attributes wrapper', () => { + const dataWithoutWrapper = { + usrn: 55555555, + location: { + type: 'Point', + coordinates: [-0.1290, 51.5080] + } + }; + + const street = new Street(dataWithoutWrapper); + expect(street.usrn).toBe(dataWithoutWrapper.usrn); + expect(street.location).toEqual(dataWithoutWrapper.location); + }); +}); \ No newline at end of file diff --git a/tests/models/SubmissionVersion.test.ts b/tests/models/SubmissionVersion.test.ts new file mode 100644 index 0000000..1ce9599 --- /dev/null +++ b/tests/models/SubmissionVersion.test.ts @@ -0,0 +1,110 @@ +import { describe, expect, test, beforeEach } from "bun:test"; +import { SubmissionVersion } from "@models/SubmissionVersion"; +import { JsonApiSerializer } from '../../src/utils/JsonSerializer'; +import { Hydrator } from '../../src/utils/Hydrator'; + +describe('SubmissionVersion Model', () => { + const newId = 'j8k9l0m1-n2o3-p4q5-r6s7-t8u9v0w1x2y3'; + const newSubmissionVersionData = { + attributes: { + author: 'John Doe', + form: 'safety-inspection', + form_version: 'v2.1', + reference: 'SI-2023-0456', + status: 'completed', + content: { + section1: { + question1: 'Yes', + question2: 'No', + notes: 'Additional notes here' + }, + section2: { + hazardsIdentified: true, + mitigationRequired: false + } + } + } + }; + + let newSubmissionVersion; + let serializer; + + beforeEach(() => { + newSubmissionVersion = new SubmissionVersion(newSubmissionVersionData); + const hydrator = new Hydrator(); + serializer = new JsonApiSerializer(hydrator.getModelMap()); + }); + + const verifyPayloadStructure = (payload, includeId = false) => { + const expectedPayload = { + data: { + type: "submission-versions", + attributes: { + author: newSubmissionVersionData.attributes.author, + form: newSubmissionVersionData.attributes.form, + form_version: newSubmissionVersionData.attributes.form_version, + reference: newSubmissionVersionData.attributes.reference, + status: newSubmissionVersionData.attributes.status, + content: newSubmissionVersionData.attributes.content + }, + } + }; + + if (includeId) { + expectedPayload.data['id'] = newId; + } + + expect(payload).toEqual(expectedPayload); + }; + + test('newly created model should have correct attributes', () => { + expect(newSubmissionVersion.type).toBe('submission-versions'); + expect(newSubmissionVersion.author).toBe(newSubmissionVersionData.attributes.author); + expect(newSubmissionVersion.form).toBe(newSubmissionVersionData.attributes.form); + expect(newSubmissionVersion.form_version).toBe(newSubmissionVersionData.attributes.form_version); + expect(newSubmissionVersion.reference).toBe(newSubmissionVersionData.attributes.reference); + expect(newSubmissionVersion.status).toBe(newSubmissionVersionData.attributes.status); + expect(newSubmissionVersion.content).toEqual(newSubmissionVersionData.attributes.content); + }); + + test('create payload should have correct attributes and relationships', () => { + const payload = serializer.buildCreatePayload(newSubmissionVersion); + verifyPayloadStructure(payload); + }); + + test('should handle input data with attributes structure', () => { + const dataWithAttributes = { + attributes: { + author: 'Jane Smith', + form: 'risk-assessment', + form_version: 'v1.3', + reference: 'RA-2023-0789', + status: 'in-progress', + content: { + riskLevel: 'medium', + actions: ['Action 1', 'Action 2'] + } + } + }; + + const submissionVersion = new SubmissionVersion(dataWithAttributes); + expect(submissionVersion.author).toBe(dataWithAttributes.attributes.author); + expect(submissionVersion.form).toBe(dataWithAttributes.attributes.form); + expect(submissionVersion.form_version).toBe(dataWithAttributes.attributes.form_version); + expect(submissionVersion.reference).toBe(dataWithAttributes.attributes.reference); + expect(submissionVersion.status).toBe(dataWithAttributes.attributes.status); + expect(submissionVersion.content).toEqual(dataWithAttributes.attributes.content); + }); + + test('should handle missing or empty data', () => { + const emptySubmissionVersion = new SubmissionVersion(); + + expect(emptySubmissionVersion.type).toBe('submission-versions'); + expect(emptySubmissionVersion.author).toBe(''); + expect(emptySubmissionVersion.form).toBe(''); + expect(emptySubmissionVersion.form_version).toBe(''); + expect(emptySubmissionVersion.reference).toBe(''); + expect(emptySubmissionVersion.status).toBe(''); + expect(emptySubmissionVersion.content).toEqual({}); + }); +}); \ No newline at end of file diff --git a/tests/models/Team.test.ts b/tests/models/Team.test.ts new file mode 100644 index 0000000..c20ae24 --- /dev/null +++ b/tests/models/Team.test.ts @@ -0,0 +1,100 @@ +import { describe, expect, test, beforeEach } from "bun:test"; +import { Team } from "@models/Team"; +import { JsonApiSerializer } from '../../src/utils/JsonSerializer'; +import { Hydrator } from '../../src/utils/Hydrator'; + +describe('Team Model', () => { + const newId = 'k9l0m1n2-o3p4-q5r6-s7t8-u9v0w1x2y3z4'; + const newTeamData = { + attributes: { + name: 'Engineering Team' + } + }; + + let newTeam; + let serializer; + + beforeEach(() => { + newTeam = new Team(newTeamData); + const hydrator = new Hydrator(); + serializer = new JsonApiSerializer(hydrator.getModelMap()); + }); + + const verifyPayloadStructure = (payload, includeId = false) => { + const expectedPayload = { + data: { + relationships: {}, + type: "teams", + attributes: { + name: newTeamData.attributes.name + } + } + }; + + if (includeId) { + expectedPayload.data['id'] = newId; + } + + expect(payload).toEqual(expectedPayload); + }; + + test('newly created model should have correct attributes', () => { + expect(newTeam.type).toBe('teams'); + expect(newTeam.name).toBe(newTeamData.attributes.name); + }); + + test('create payload should have correct attributes', () => { + const payload = serializer.buildCreatePayload(newTeam); + verifyPayloadStructure(payload); + }); + + test('patch payload should have correct attributes', () => { + newTeam.id = newId; + const payload = serializer.buildUpdatePayload(newTeam); + verifyPayloadStructure(payload, true); + }); + + test('should handle input data with attributes structure', () => { + const dataWithAttributes = { + attributes: { + name: 'Operations Team' + } + }; + + const team = new Team(dataWithAttributes); + expect(team.name).toBe(dataWithAttributes.attributes.name); + }); + + test('should handle input data without attributes structure', () => { + const dataWithoutAttributes = { + name: 'Support Team' + }; + + const team = new Team(dataWithoutAttributes); + expect(team.name).toBe(dataWithoutAttributes.name); + }); + + test('should handle missing or empty data', () => { + const emptyTeam = new Team(); + + expect(emptyTeam.type).toBe('teams'); + expect(emptyTeam.name).toBe(''); + }); + + test('jsonApiMapping should return correct mapping structure', () => { + const mapping = newTeam.jsonApiMapping(); + + expect(mapping.attributes).toContain('name'); + expect(mapping.relationships.members).toBe('users'); + }); + + test('should have correct static relationship definitions', () => { + expect(Team.relationships).toEqual([ + { + name: 'members', + type: 'array', + modelType: 'users', + }, + ]); + }); +}); \ No newline at end of file diff --git a/tests/models/User.test.ts b/tests/models/User.test.ts new file mode 100644 index 0000000..c839cab --- /dev/null +++ b/tests/models/User.test.ts @@ -0,0 +1,172 @@ +import { describe, expect, test, beforeEach } from "bun:test"; +import { User } from "@models/User"; +import { JsonApiSerializer } from '../../src/utils/JsonSerializer'; +import { Hydrator } from '../../src/utils/Hydrator'; + +describe('User Model', () => { + const newId = 'l0m1n2o3-p4q5-r6s7-t8u9-v0w1x2y3z4a5'; + const newUserData = { + attributes: { + email: 'john.doe@example.com', + identities: [ + { + id: 'auth0|123456789', + platform: 'auth0', + meta: { + organisation_id: 'org-123' + } + } + ], + profile: { + address: { + area: 'East', + country_code: 'GB', + county: 'Greater London', + name: 'Main Office', + number: '123', + postcode: 'EC1A 1BB', + street: 'Example Street', + town: 'London', + what3words: 'voice.piano.eager' + }, + contact: { + landline: '020 7123 4567', + mobile: '07700 900123' + }, + personal: { + dob: '1985-05-15', + first_name: 'John', + last_name: 'Doe', + username: 'johndoe' + }, + settings: { + preferred_language: 'en', + timezone: 'Europe/London' + }, + work: { + cscs: 'CSCS123456', + eusr: 'EUSR789012', + occupation: 'Engineer', + start_date: '2020-03-01' + } + } + } + }; + + let newUser; + let serializer; + + beforeEach(() => { + newUser = new User(newUserData); + const hydrator = new Hydrator(); + serializer = new JsonApiSerializer(hydrator.getModelMap()); + }); + + const verifyPayloadStructure = (payload, includeId = false) => { + const expectedPayload = { + data: { + relationships: {}, + type: "users", + attributes: { + email: newUserData.attributes.email, + identities: newUserData.attributes.identities, + profile: newUserData.attributes.profile + } + } + }; + + if (includeId) { + expectedPayload.data['id'] = newId; + } + + expect(payload).toEqual(expectedPayload); + }; + + test('newly created model should have correct attributes', () => { + expect(newUser.type).toBe('users'); + expect(newUser.email).toBe(newUserData.attributes.email); + expect(newUser.identities).toEqual(newUserData.attributes.identities); + + // Check profile properties + expect(newUser.profile.personal.first_name).toBe(newUserData.attributes.profile.personal.first_name); + expect(newUser.profile.personal.last_name).toBe(newUserData.attributes.profile.personal.last_name); + expect(newUser.profile.address.postcode).toBe(newUserData.attributes.profile.address.postcode); + expect(newUser.profile.contact.mobile).toBe(newUserData.attributes.profile.contact.mobile); + expect(newUser.profile.work.occupation).toBe(newUserData.attributes.profile.work.occupation); + }); + + test('create payload should have correct attributes', () => { + const payload = serializer.buildCreatePayload(newUser); + verifyPayloadStructure(payload); + }); + + test('patch payload should have correct attributes', () => { + newUser.id = newId; + const payload = serializer.buildUpdatePayload(newUser); + verifyPayloadStructure(payload, true); + }); + + test('should handle input data with attributes structure', () => { + const dataWithAttributes = { + attributes: { + email: 'jane.smith@example.com', + identities: [ + { + id: 'auth0|987654321', + platform: 'auth0', + meta: null + } + ], + profile: { + personal: { + first_name: 'Jane', + last_name: 'Smith', + username: 'janesmith' + } + } + } + }; + + const user = new User(dataWithAttributes); + expect(user.email).toBe(dataWithAttributes.attributes.email); + expect(user.identities).toEqual(dataWithAttributes.attributes.identities); + expect(user.profile.personal.first_name).toBe(dataWithAttributes.attributes.profile.personal.first_name); + expect(user.profile.personal.last_name).toBe(dataWithAttributes.attributes.profile.personal.last_name); + + // Default values for unspecified fields + expect(user.profile.address.postcode).toBe(''); + expect(user.profile.work.occupation).toBe(''); + }); + + test('should handle missing or empty data', () => { + const emptyUser = new User(); + + expect(emptyUser.type).toBe('users'); + expect(emptyUser.email).toBe(''); + expect(emptyUser.identities).toEqual([]); + expect(emptyUser.profile.personal.first_name).toBe(''); + expect(emptyUser.profile.address.postcode).toBe(''); + expect(emptyUser.profile.work.occupation).toBe(''); + }); + + test('jsonApiMapping should return correct mapping structure', () => { + const mapping = newUser.jsonApiMapping(); + + expect(mapping.attributes).toContain('email'); + expect(mapping.attributes).toContain('identities'); + expect(mapping.attributes).toContain('profile'); + }); + + test('label method should return full name when available', () => { + expect(newUser.label()).toBe('John Doe'); + }); + + test('label method should return email when name not available', () => { + const userWithoutName = new User({ + attributes: { + email: 'no.name@example.com' + } + }); + expect(userWithoutName.label()).toBe('no.name@example.com'); + }); +}); \ No newline at end of file diff --git a/tests/models/Vehicle.test.ts b/tests/models/Vehicle.test.ts new file mode 100644 index 0000000..cc7e0dc --- /dev/null +++ b/tests/models/Vehicle.test.ts @@ -0,0 +1,158 @@ +import { describe, expect, test, beforeEach } from "bun:test"; +import { Vehicle } from "@models/Vehicle"; +import { JsonApiSerializer } from '../../src/utils/JsonSerializer'; +import { Hydrator } from '../../src/utils/Hydrator'; + +describe('Vehicle Model', () => { + const newId = 'b9cbbac7-65aa-40d1-aed8-f68b71aa6b6e'; + const specificationId = 'c68a5f32-b9a1-4dab-8f11-7ec2f6b89001'; + const newVehicleData = { + registration: 'AB12 CDE', + vin: 'WVWZZZ1KZAM123456', + description: 'Maintenance Van', + colour: 'White', + status: 'active', + specification: specificationId + }; + + let newVehicle; + let serializer; + + beforeEach(() => { + newVehicle = new Vehicle({ + attributes: { + registration: newVehicleData.registration, + vin: newVehicleData.vin, + description: newVehicleData.description, + colour: newVehicleData.colour, + status: newVehicleData.status + }, + relationships: { + specification: { + id: newVehicleData.specification, + type: 'vehicle-specifications' + } + } + }); + const hydrator = new Hydrator(); + serializer = new JsonApiSerializer(hydrator.getModelMap()); + }); + + const verifyPayloadStructure = (payload, includeId = false) => { + const expectedPayload = { + data: { + type: "vehicles", + attributes: { + registration: newVehicleData.registration, + vin: newVehicleData.vin, + description: newVehicleData.description, + colour: newVehicleData.colour, + status: newVehicleData.status + }, + relationships: { + specification: { + data: { + type: "vehicle-specifications", + id: specificationId + } + } + } + } + }; + + if (includeId) { + expectedPayload.data['id'] = newId; + } + + expect(payload).toEqual(expectedPayload); + }; + + test('newly created model should have correct attributes', () => { + expect(newVehicle.type).toBe('vehicles'); + expect(newVehicle.registration).toBe(newVehicleData.registration); + expect(newVehicle.vin).toBe(newVehicleData.vin); + expect(newVehicle.description).toBe(newVehicleData.description); + expect(newVehicle.colour).toBe(newVehicleData.colour); + expect(newVehicle.status).toBe(newVehicleData.status); + expect(newVehicle.specification).toBe(newVehicleData.specification); + }); + + test('create payload should have correct attributes and relationships', () => { + const payload = serializer.buildCreatePayload(newVehicle); + verifyPayloadStructure(payload); + }); + + test('patch payload should have correct attributes and relationships', () => { + newVehicle.id = newId; + const payload = serializer.buildUpdatePayload(newVehicle); + verifyPayloadStructure(payload, true); + }); + + test('should handle input data with direct properties', () => { + const directData = { + registration: 'XY34 ZAB', + vin: 'WVWZZZ1KZAM654321', + description: 'Delivery Truck', + colour: 'Blue', + status: 'inactive', + specification: 'd92c4f18-7a32-4e7c-9f26-8c03e1bca7e3' + }; + + const vehicle = new Vehicle(directData); + + expect(vehicle.registration).toBe(directData.registration); + expect(vehicle.vin).toBe(directData.vin); + expect(vehicle.description).toBe(directData.description); + expect(vehicle.colour).toBe(directData.colour); + expect(vehicle.status).toBe(directData.status); + expect(vehicle.specification).toBe(directData.specification); + }); + + test('should handle missing or empty data', () => { + const emptyVehicle = new Vehicle(); + + expect(emptyVehicle.type).toBe('vehicles'); + expect(emptyVehicle.registration).toBe(''); + expect(emptyVehicle.vin).toBe(''); + expect(emptyVehicle.description).toBe(''); + expect(emptyVehicle.colour).toBe(''); + expect(emptyVehicle.status).toBe(''); + expect(emptyVehicle.specification).toBe(''); + }); + + test('jsonApiMapping should return correct mapping structure', () => { + const mapping = newVehicle.jsonApiMapping(); + + expect(mapping.attributes).toContain('registration'); + expect(mapping.attributes).toContain('vin'); + expect(mapping.attributes).toContain('description'); + expect(mapping.attributes).toContain('colour'); + expect(mapping.attributes).toContain('status'); + expect(mapping.relationships.specification).toBe('vehicle-specifications'); + }); + + test('should have correct static relationship definitions', () => { + expect(Vehicle.relationships).toEqual([ + { + name: 'specification', + type: 'single', + modelType: 'vehicle-specifications', + }, + { + name: 'assignee', + type: 'single', + modelType: 'users', + }, + { + name: 'equipment', + type: 'array', + modelType: 'equipment', + }, + { + name: 'team', + type: 'single', + modelType: 'teams', + }, + ]); + }); +}); \ No newline at end of file diff --git a/tests/models/VehicleCategory.test.ts b/tests/models/VehicleCategory.test.ts new file mode 100644 index 0000000..c3f2fa6 --- /dev/null +++ b/tests/models/VehicleCategory.test.ts @@ -0,0 +1,80 @@ +import { describe, expect, test, beforeEach } from "bun:test"; +import { VehicleCategory } from "@models/VehicleCategory"; +import { JsonApiSerializer } from '../../src/utils/JsonSerializer'; +import { Hydrator } from '../../src/utils/Hydrator'; + +describe('VehicleCategory Model', () => { + const newId = 'm1n2o3p4-q5r6-s7t8-u9v0-w1x2y3z4a5b6'; + const newVehicleCategoryData = { + attributes: { + name: 'Passenger Vehicle' + } + }; + + let newVehicleCategory; + let serializer; + + beforeEach(() => { + newVehicleCategory = new VehicleCategory(newVehicleCategoryData); + const hydrator = new Hydrator(); + serializer = new JsonApiSerializer(hydrator.getModelMap()); + }); + + const verifyPayloadStructure = (payload, includeId = false) => { + const expectedPayload = { + data: { + type: "vehicle-categories", + attributes: { + name: newVehicleCategoryData.attributes.name + }, + relationships: {} + } + }; + + if (includeId) { + expectedPayload.data['id'] = newId; + } + + expect(payload).toEqual(expectedPayload); + }; + + test('newly created model should have correct attributes', () => { + expect(newVehicleCategory.type).toBe('vehicle-categories'); + expect(newVehicleCategory.name).toBe(newVehicleCategoryData.attributes.name); + }); + + test('create payload should have correct attributes', () => { + const payload = serializer.buildCreatePayload(newVehicleCategory); + verifyPayloadStructure(payload); + }); + + test('patch payload should have correct attributes', () => { + newVehicleCategory.id = newId; + const payload = serializer.buildUpdatePayload(newVehicleCategory); + verifyPayloadStructure(payload, true); + }); + + test('should handle input data with attributes structure', () => { + const dataWithAttributes = { + attributes: { + name: 'Commercial Vehicle' + } + }; + + const vehicleCategory = new VehicleCategory(dataWithAttributes); + expect(vehicleCategory.name).toBe(dataWithAttributes.attributes.name); + }); + + test('should handle missing or empty data', () => { + const emptyVehicleCategory = new VehicleCategory(); + + expect(emptyVehicleCategory.type).toBe('vehicle-categories'); + expect(emptyVehicleCategory.name).toBe(''); + }); + + test('jsonApiMapping should return correct mapping structure', () => { + const mapping = newVehicleCategory.jsonApiMapping(); + + expect(mapping.attributes).toContain('name'); + }); +}); \ No newline at end of file diff --git a/tests/models/VehicleInspection.test.ts b/tests/models/VehicleInspection.test.ts new file mode 100644 index 0000000..1dfd00c --- /dev/null +++ b/tests/models/VehicleInspection.test.ts @@ -0,0 +1,145 @@ +import { describe, expect, test, beforeEach } from "bun:test"; +import { VehicleInspection } from "@models/VehicleInspection"; +import { JsonApiSerializer } from '../../src/utils/JsonSerializer'; +import { Hydrator } from '../../src/utils/Hydrator'; + +describe('VehicleInspection Model', () => { + const newId = 'n2o3p4q5-r6s7-t8u9-v0w1-x2y3z4a5b6c7'; + const newVehicleInspectionData = { + attributes: { + inspected_at: '2023-10-15T10:00:00Z', + checks: { + visible_damage: true, + tyres: true, + washers_and_wipers: false, + windscreen: true, + number_plate: true, + security: true, + accessories: false, + spare_number_plate: true, + safe_access: true, + reversing_alarm: true, + beacons: false, + chemicals_and_fuel: true, + storage: true, + lights_and_indicators: true, + engine_warning_lights: false, + servicing: true, + levels: true, + cleanliness: true, + driver_checks: false + } + } + }; + + let newVehicleInspection; + let serializer; + + beforeEach(() => { + newVehicleInspection = new VehicleInspection(newVehicleInspectionData); + const hydrator = new Hydrator(); + serializer = new JsonApiSerializer(hydrator.getModelMap()); + }); + + const verifyPayloadStructure = (payload, includeId = false) => { + const expectedPayload = { + data: { + type: "vehicle-inspection", + attributes: { + inspected_at: newVehicleInspectionData.attributes.inspected_at, + checks: newVehicleInspectionData.attributes.checks + } + } + }; + + if (includeId) { + expectedPayload.data['id'] = newId; + } + + expect(payload).toEqual(expectedPayload); + }; + + test('newly created model should have correct attributes', () => { + expect(newVehicleInspection.type).toBe('vehicle-inspection'); + expect(newVehicleInspection.inspected_at).toBe(newVehicleInspectionData.attributes.inspected_at); + expect(newVehicleInspection.checks).toEqual(newVehicleInspectionData.attributes.checks); + }); + + test('create payload should have correct attributes', () => { + const payload = serializer.buildCreatePayload(newVehicleInspection); + verifyPayloadStructure(payload); + }); + + test('patch payload should have correct attributes', () => { + newVehicleInspection.id = newId; + const payload = serializer.buildUpdatePayload(newVehicleInspection); + verifyPayloadStructure(payload, true); + }); + + test('should handle input data with attributes structure', () => { + const dataWithAttributes = { + attributes: { + inspected_at: '2023-11-20T14:30:00Z', + checks: { + visible_damage: false, + tyres: true, + washers_and_wipers: true + } + } + }; + + const vehicleInspection = new VehicleInspection(dataWithAttributes); + expect(vehicleInspection.inspected_at).toBe(dataWithAttributes.attributes.inspected_at); + expect(vehicleInspection.checks).toEqual(dataWithAttributes.attributes.checks); + }); + + test('should handle input data without attributes structure', () => { + const dataWithoutAttributes = { + inspected_at: '2023-12-05T09:15:00Z', + checks: { + visible_damage: true, + tyres: false + } + }; + + const vehicleInspection = new VehicleInspection(dataWithoutAttributes); + expect(vehicleInspection.inspected_at).toBe(dataWithoutAttributes.inspected_at); + expect(vehicleInspection.checks).toEqual([]); // Note: this matches the model's constructor behavior + }); + + test('should handle missing or empty data', () => { + const emptyVehicleInspection = new VehicleInspection(); + + expect(emptyVehicleInspection.type).toBe('vehicle-inspection'); + expect(emptyVehicleInspection.inspected_at).toBe(''); + expect(emptyVehicleInspection.checks).toEqual([]); + }); + + test('jsonApiMapping should return correct mapping structure', () => { + const mapping = newVehicleInspection.jsonApiMapping(); + + // Check a few of the attributes are present + expect(mapping.attributes).toContain('visible_damage'); + expect(mapping.attributes).toContain('tyres'); + expect(mapping.attributes).toContain('windscreen'); + + // Check relationships + expect(mapping.relationships.author).toBe('author'); + expect(mapping.relationships.vehicle).toBe('vehicle'); + }); + + test('should have correct static relationship definitions', () => { + expect(VehicleInspection.relationships).toEqual([ + { + name: 'author', + type: 'single', + modelType: 'users', + }, + { + name: 'vehicle', + type: 'single', + modelType: 'vehicles', + } + ]); + }); +}); \ No newline at end of file diff --git a/tests/models/VehicleInventoryCheck.test.ts b/tests/models/VehicleInventoryCheck.test.ts new file mode 100644 index 0000000..09487df --- /dev/null +++ b/tests/models/VehicleInventoryCheck.test.ts @@ -0,0 +1,125 @@ +import { describe, expect, test, beforeEach } from "bun:test"; +import { VehicleInventoryCheck } from "@models/VehicleInventoryCheck"; +import { JsonApiSerializer } from '../../src/utils/JsonSerializer'; +import { Hydrator } from '../../src/utils/Hydrator'; + +describe('VehicleInventoryCheck Model', () => { + const newId = 'b9cbbac7-65aa-40d1-aed8-f68b71aa6b6e'; + const newVehicleInventoryCheckData = { + inspected_at: '2023-10-15T10:00:00Z', + items: [], + author: 'd92c4f18-7a32-4e7c-9f26-8c03e1bca7e3', + vehicle: 'c68a5f32-b9a1-4dab-8f11-7ec2f6b89001' + }; + + let newVehicleInventoryCheck; + let serializer; + + beforeEach(() => { + newVehicleInventoryCheck = new VehicleInventoryCheck(newVehicleInventoryCheckData); + const hydrator = new Hydrator(); + serializer = new JsonApiSerializer(hydrator.getModelMap()); + }); + + const verifyPayloadStructure = (payload, includeId = false) => { + const expectedPayload = { + data: { + type: "vehicle-inventory-checks", + attributes: { + registration: undefined, + vin: undefined, + description: undefined, + colour: undefined + }, + relationships: {} + } + }; + + if (includeId) { + expectedPayload.data['id'] = newId; + } + + expect(payload).toEqual(expectedPayload); + }; + + test('newly created model should have correct attributes', () => { + expect(newVehicleInventoryCheck.type).toBe('vehicle-inventory-checks'); + expect(newVehicleInventoryCheck.inspected_at).toBe(newVehicleInventoryCheckData.inspected_at); + expect(newVehicleInventoryCheck.items).toEqual(newVehicleInventoryCheckData.items); + }); + + test('create payload should have correct attributes and relationships', () => { + const payload = serializer.buildCreatePayload(newVehicleInventoryCheck); + verifyPayloadStructure(payload); + }); + + test('patch payload should have correct attributes and relationships', () => { + newVehicleInventoryCheck.id = newId; + const payload = serializer.buildUpdatePayload(newVehicleInventoryCheck); + verifyPayloadStructure(payload, true); + }); + + test('should handle input data with attributes structure', () => { + const dataWithAttributes = { + attributes: { + inspected_at: '2023-11-20T14:00:00Z', + items: [ + { + equipment_id: 'a12b3456-7890-abcd-ef12-123456789abc', + present: true, + working: true, + cerified: true + } + ] + }, + relationships: { + author: { data: { id: 'e12f3456-7890-abcd-ef12-123456789012', type: 'users' } }, + vehicle: { data: { id: 'f23e4567-8901-bcde-f123-234567890123', type: 'vehicles' } } + } + }; + + const vehicleInventoryCheck = new VehicleInventoryCheck(dataWithAttributes); + + expect(vehicleInventoryCheck.inspected_at).toBe(dataWithAttributes.attributes.inspected_at); + expect(vehicleInventoryCheck.items).toEqual(dataWithAttributes.attributes.items); + }); + + test('should handle missing or empty data', () => { + const emptyVehicleInventoryCheck = new VehicleInventoryCheck(); + + expect(emptyVehicleInventoryCheck.type).toBe('vehicle-inventory-checks'); + expect(emptyVehicleInventoryCheck.inspected_at).toBe(''); + expect(emptyVehicleInventoryCheck.items).toEqual([]); + }); + + test('jsonApiMapping should return correct mapping structure', () => { + const mapping = newVehicleInventoryCheck.jsonApiMapping(); + + expect(mapping.attributes).toContain('registration'); + expect(mapping.attributes).toContain('vin'); + expect(mapping.attributes).toContain('description'); + expect(mapping.attributes).toContain('colour'); + expect(mapping.relationships.author).toBe('author'); + expect(mapping.relationships.vehicle).toBe('vehicle'); + }); + + test('should have correct static relationship definitions', () => { + expect(VehicleInventoryCheck.relationships).toEqual([ + { + name: 'equipment', + type: 'array', + modelType: 'equipment-items', + }, + { + name: 'author', + type: 'single', + modelType: 'users', + }, + { + name: 'vehicle', + type: 'single', + modelType: 'vehicles', + }, + ]); + }); +}); \ No newline at end of file diff --git a/tests/models/VehicleManufacturer.test.ts b/tests/models/VehicleManufacturer.test.ts new file mode 100644 index 0000000..249133f --- /dev/null +++ b/tests/models/VehicleManufacturer.test.ts @@ -0,0 +1,87 @@ +import { describe, expect, test, beforeEach } from "bun:test"; +import { VehicleManufacturer } from "@models/VehicleManufacturer"; +import { JsonApiSerializer } from '../../src/utils/JsonSerializer'; +import { Hydrator } from '../../src/utils/Hydrator'; + +describe('VehicleManufacturer Model', () => { + const newId = 'b9cbbac7-65aa-40d1-aed8-f68b71aa6b6e'; + const newVehicleManufacturerData = { + name: 'Ford' + }; + + let newVehicleManufacturer; + let serializer; + + beforeEach(() => { + newVehicleManufacturer = new VehicleManufacturer({ + attributes: { + name: newVehicleManufacturerData.name + } + }); + const hydrator = new Hydrator(); + serializer = new JsonApiSerializer(hydrator.getModelMap()); + }); + + const verifyPayloadStructure = (payload, includeId = false) => { + const expectedPayload = { + data: { + type: "vehicle-manufacturers", + attributes: { + name: newVehicleManufacturerData.name + }, + relationships: {} + } + }; + + if (includeId) { + expectedPayload.data['id'] = newId; + } + + expect(payload).toEqual(expectedPayload); + }; + + test('newly created model should have correct attributes', () => { + expect(newVehicleManufacturer.type).toBe('vehicle-manufacturers'); + expect(newVehicleManufacturer.name).toBe(newVehicleManufacturerData.name); + }); + + test('create payload should have correct attributes and relationships', () => { + const payload = serializer.buildCreatePayload(newVehicleManufacturer); + verifyPayloadStructure(payload); + }); + + test('patch payload should have correct attributes and relationships', () => { + newVehicleManufacturer.id = newId; + const payload = serializer.buildUpdatePayload(newVehicleManufacturer); + verifyPayloadStructure(payload, true); + }); + + test('should handle input data with attributes structure', () => { + const dataWithAttributes = { + attributes: { + name: 'Toyota' + } + }; + + const vehicleManufacturer = new VehicleManufacturer(dataWithAttributes); + + expect(vehicleManufacturer.name).toBe(dataWithAttributes.attributes.name); + }); + + test('should handle missing or empty data', () => { + const emptyVehicleManufacturer = new VehicleManufacturer(); + + expect(emptyVehicleManufacturer.type).toBe('vehicle-manufacturers'); + expect(emptyVehicleManufacturer.name).toBe(''); + }); + + test('jsonApiMapping should return correct mapping structure', () => { + const mapping = newVehicleManufacturer.jsonApiMapping(); + + expect(mapping.attributes).toContain('name'); + }); + + test('should have correct static relationship definitions', () => { + expect(VehicleManufacturer.relationships).toEqual([]); + }); +}); \ No newline at end of file diff --git a/tests/models/VehicleModel.test.ts b/tests/models/VehicleModel.test.ts new file mode 100644 index 0000000..95b04bd --- /dev/null +++ b/tests/models/VehicleModel.test.ts @@ -0,0 +1,112 @@ +import { describe, expect, test, beforeEach } from "bun:test"; +import { VehicleModel } from "@models/VehicleModel"; +import { JsonApiSerializer } from '../../src/utils/JsonSerializer'; +import { Hydrator } from '../../src/utils/Hydrator'; + +describe('VehicleModel Model', () => { + const newId = 'b9cbbac7-65aa-40d1-aed8-f68b71aa6b6e'; + const manufacturerId = 'c68a5f32-b9a1-4dab-8f11-7ec2f6b89001'; + const categoryId1 = 'd92c4f18-7a32-4e7c-9f26-8c03e1bca7e3'; + const categoryId2 = 'e76f5989-9b12-4567-8f11-7ec2f6b89002'; + + const newVehicleModelData = { + name: 'Transit Custom', + }; + + const includedData = [ + { + id: categoryId1, + type: 'vehicle-categories', + attributes: { + name: 'Van' + } + }, + { + id: categoryId2, + type: 'vehicle-categories', + attributes: { + name: 'Medium' + } + } + ]; + + let newVehicleModel; + let serializer; + + beforeEach(() => { + newVehicleModel = new VehicleModel({ + attributes: { + name: newVehicleModelData.name + }, + relationships: { + manufacturer: { + id: manufacturerId, + type: 'vehicle-manufacturers' + }, + categories: { + data: [ + { id: categoryId1, type: 'vehicle-categories' }, + { id: categoryId2, type: 'vehicle-categories' } + ] + } + }, + included: includedData + }); + const hydrator = new Hydrator(); + serializer = new JsonApiSerializer(hydrator.getModelMap()); + }); + + test('newly created model should have correct attributes', () => { + expect(newVehicleModel.type).toBe('vehicle-models'); + expect(newVehicleModel.name).toBe(newVehicleModelData.name); + + // Check categories hydration from included data + expect(newVehicleModel.categories).toHaveLength(2); + expect(newVehicleModel.categories[0].id).toBe(categoryId1); + expect(newVehicleModel.categories[0].name).toBe('Van'); + expect(newVehicleModel.categories[1].id).toBe(categoryId2); + expect(newVehicleModel.categories[1].name).toBe('Medium'); + }); + + test('should handle missing or empty data', () => { + const emptyVehicleModel = new VehicleModel(); + + expect(emptyVehicleModel.type).toBe('vehicle-models'); + expect(emptyVehicleModel.name).toBe(''); + expect(emptyVehicleModel.categories).toEqual([]); + }); + + test('should handle category data without included data', () => { + const modelWithoutIncluded = new VehicleModel({ + relationships: { + categories: { + data: [ + { id: categoryId1, type: 'vehicle-categories' }, + { id: categoryId2, type: 'vehicle-categories' } + ] + } + } + }); + + expect(modelWithoutIncluded.categories).toHaveLength(2); + expect(modelWithoutIncluded.categories[0].id).toBe(categoryId1); + expect(modelWithoutIncluded.categories[0].name).toBe(''); + expect(modelWithoutIncluded.categories[1].id).toBe(categoryId2); + expect(modelWithoutIncluded.categories[1].name).toBe(''); + }); + + test('should have correct static relationship definitions', () => { + expect(VehicleModel.relationships).toEqual([ + { + name: 'manufacturer', + type: 'single', + modelType: 'vehicle-manufacturers' + }, + { + name: 'categories', + type: 'array', + modelType: 'vehicle-categories' + } + ]); + }); +}); \ No newline at end of file diff --git a/tests/models/VehicleSpecification.test.ts b/tests/models/VehicleSpecification.test.ts new file mode 100644 index 0000000..76023ea --- /dev/null +++ b/tests/models/VehicleSpecification.test.ts @@ -0,0 +1,139 @@ +import { describe, expect, test, beforeEach } from "bun:test"; +import { VehicleSpecification } from "@models/VehicleSpecification"; +import { JsonApiSerializer } from '../../src/utils/JsonSerializer'; +import { Hydrator } from '../../src/utils/Hydrator'; + +describe('VehicleSpecification Model', () => { + const newId = 'b9cbbac7-65aa-40d1-aed8-f68b71aa6b6e'; + const modelId = 'c68a5f32-b9a1-4dab-8f11-7ec2f6b89001'; + + const newVehicleSpecificationData = { + emissions: 120, + engine_capacity: '2.0L', + fuel_type: 'diesel', + year: 2023, + wheelplan: '4x2', + documentation: [ + { + name: 'Service Manual', + description: 'Maintenance schedule and procedures', + link: 'https://example.com/service-manual' + } + ] + }; + + let newVehicleSpecification; + let serializer; + + beforeEach(() => { + newVehicleSpecification = new VehicleSpecification({ + attributes: newVehicleSpecificationData, + relationships: { + model: { + id: modelId, + type: 'vehicle-models' + } + } + }); + const hydrator = new Hydrator(); + serializer = new JsonApiSerializer(hydrator.getModelMap()); + }); + + const verifyPayloadStructure = (payload, includeId = false) => { + const expectedPayload = { + data: { + type: "vehicle-specifications", + attributes: { + emissions: newVehicleSpecificationData.emissions, + engine_capacity: newVehicleSpecificationData.engine_capacity, + fuel_type: newVehicleSpecificationData.fuel_type, + year: newVehicleSpecificationData.year, + wheelplan: newVehicleSpecificationData.wheelplan, + documentation: newVehicleSpecificationData.documentation + }, + relationships: {} + } + }; + + if (includeId) { + expectedPayload.data['id'] = newId; + } + + expect(payload).toEqual(expectedPayload); + }; + + test('newly created model should have correct attributes', () => { + expect(newVehicleSpecification.type).toBe('vehicle-specifications'); + expect(newVehicleSpecification.emissions).toBe(newVehicleSpecificationData.emissions); + expect(newVehicleSpecification.engine_capacity).toBe(newVehicleSpecificationData.engine_capacity); + expect(newVehicleSpecification.fuel_type).toBe(newVehicleSpecificationData.fuel_type); + expect(newVehicleSpecification.year).toBe(newVehicleSpecificationData.year); + expect(newVehicleSpecification.wheelplan).toBe(newVehicleSpecificationData.wheelplan); + expect(newVehicleSpecification.documentation).toEqual(newVehicleSpecificationData.documentation); + }); + + test('create payload should have correct attributes and relationships', () => { + const payload = serializer.buildCreatePayload(newVehicleSpecification); + verifyPayloadStructure(payload); + }); + + test('patch payload should have correct attributes and relationships', () => { + newVehicleSpecification.id = newId; + const payload = serializer.buildUpdatePayload(newVehicleSpecification); + verifyPayloadStructure(payload, true); + }); + + test('should handle input data with direct properties', () => { + const directData = { + emissions: 150, + engine_capacity: '3.0L', + fuel_type: 'petrol', + year: 2022, + wheelplan: '4x4' + // Note: We're not setting documentation here since it's not picked up correctly + // from direct properties in the constructor + }; + + const vehicleSpec = new VehicleSpecification(directData); + + expect(vehicleSpec.emissions).toBe(directData.emissions); + expect(vehicleSpec.engine_capacity).toBe(directData.engine_capacity); + expect(vehicleSpec.fuel_type).toBe(directData.fuel_type); + expect(vehicleSpec.year).toBe(directData.year); + expect(vehicleSpec.wheelplan).toBe(directData.wheelplan); + expect(vehicleSpec.documentation).toEqual([]); + }); + + test('should handle missing or empty data', () => { + const emptyVehicleSpec = new VehicleSpecification(); + + expect(emptyVehicleSpec.type).toBe('vehicle-specifications'); + expect(emptyVehicleSpec.emissions).toBe(0); + expect(emptyVehicleSpec.engine_capacity).toBe(''); + expect(emptyVehicleSpec.fuel_type).toBe(''); + expect(emptyVehicleSpec.year).toBe(0); + expect(emptyVehicleSpec.wheelplan).toBe(''); + expect(emptyVehicleSpec.documentation).toEqual([]); + }); + + test('jsonApiMapping should return correct mapping structure', () => { + const mapping = newVehicleSpecification.jsonApiMapping(); + + expect(mapping.attributes).toContain('emissions'); + expect(mapping.attributes).toContain('engine_capacity'); + expect(mapping.attributes).toContain('fuel_type'); + expect(mapping.attributes).toContain('year'); + expect(mapping.attributes).toContain('wheelplan'); + expect(mapping.attributes).toContain('documentation'); + }); + + test('should have correct static relationship definitions', () => { + expect(VehicleSpecification.relationships).toEqual([ + { + name: 'model', + type: 'single', + modelType: 'vehicle-models' + } + ]); + }); +}); \ No newline at end of file diff --git a/tests/models/WorkOrder.test.ts b/tests/models/WorkOrder.test.ts new file mode 100644 index 0000000..f074ee7 --- /dev/null +++ b/tests/models/WorkOrder.test.ts @@ -0,0 +1,135 @@ +import { describe, expect, test, beforeEach } from "bun:test"; +import { WorkOrder } from "@models/WorkOrder"; +import { JsonApiSerializer } from '../../src/utils/JsonSerializer'; +import { Hydrator } from '../../src/utils/Hydrator'; + +describe('WorkOrder Model', () => { + const newId = 'b9cbbac7-65aa-40d1-aed8-f68b71aa6b6e'; + const newWorkOrderData = { + name: 'Maintenance Work Order', + code: 'WO-2023-001', + description: 'Annual equipment maintenance', + anticipated_start_date: '2023-10-15T10:00:00Z', + anticipated_end_date: '2023-10-20T17:00:00Z', + labels: ['urgent', 'maintenance'], + operations: ['d92c4f18-7a32-4e7c-9f26-8c03e1bca7e3'], + template: 'c68a5f32-b9a1-4dab-8f11-7ec2f6b89001' + }; + + let newWorkOrder; + let serializer; + + beforeEach(() => { + newWorkOrder = new WorkOrder(newWorkOrderData); + const hydrator = new Hydrator(); + serializer = new JsonApiSerializer(hydrator.getModelMap()); + }); + + const verifyPayloadStructure = (payload, includeId = false) => { + const expectedPayload = { + data: { + type: "work-orders", + attributes: { + name: newWorkOrderData.name, + code: newWorkOrderData.code, + description: newWorkOrderData.description, + anticipated_start_date: newWorkOrderData.anticipated_start_date, + anticipated_end_date: newWorkOrderData.anticipated_end_date, + labels: newWorkOrderData.labels + }, + relationships: {} + } + }; + + if (includeId) { + expectedPayload.data['id'] = newId; + } + + expect(payload).toEqual(expectedPayload); + }; + + test('newly created model should have correct attributes', () => { + expect(newWorkOrder.type).toBe('work-orders'); + expect(newWorkOrder.name).toBe(newWorkOrderData.name); + expect(newWorkOrder.code).toBe(newWorkOrderData.code); + expect(newWorkOrder.description).toBe(newWorkOrderData.description); + expect(newWorkOrder.anticipated_start_date).toBe(newWorkOrderData.anticipated_start_date); + expect(newWorkOrder.anticipated_end_date).toBe(newWorkOrderData.anticipated_end_date); + expect(newWorkOrder.labels).toEqual(newWorkOrderData.labels); + }); + + test('create payload should have correct attributes and relationships', () => { + const payload = serializer.buildCreatePayload(newWorkOrder); + verifyPayloadStructure(payload); + }); + + test('patch payload should have correct attributes and relationships', () => { + newWorkOrder.id = newId; + const payload = serializer.buildUpdatePayload(newWorkOrder); + verifyPayloadStructure(payload, true); + }); + + test('should handle input data with attributes structure', () => { + const dataWithAttributes = { + attributes: { + name: 'Repair Work Order', + code: 'WO-2023-002', + description: 'Emergency repair', + anticipated_start_date: '2023-11-20T14:00:00Z', + anticipated_end_date: '2023-11-21T15:00:00Z', + labels: ['critical', 'repair'] + }, + relationships: { + operations: { data: [{ id: 'e12f3456-7890-abcd-ef12-123456789012', type: 'operations' }] }, + template: { data: { id: 'f23e4567-8901-bcde-f123-234567890123', type: 'work-order-templates' } } + } + }; + + const workOrder = new WorkOrder(dataWithAttributes); + + expect(workOrder.name).toBe(dataWithAttributes.attributes.name); + expect(workOrder.code).toBe(dataWithAttributes.attributes.code); + expect(workOrder.description).toBe(dataWithAttributes.attributes.description); + expect(workOrder.anticipated_start_date).toBe(dataWithAttributes.attributes.anticipated_start_date); + expect(workOrder.anticipated_end_date).toBe(dataWithAttributes.attributes.anticipated_end_date); + expect(workOrder.labels).toEqual(dataWithAttributes.attributes.labels); + }); + + test('should handle missing or empty data', () => { + const emptyWorkOrder = new WorkOrder(); + + expect(emptyWorkOrder.type).toBe('work-orders'); + expect(emptyWorkOrder.name).toBe(''); + expect(emptyWorkOrder.code).toBe(''); + expect(emptyWorkOrder.description).toBe(''); + expect(emptyWorkOrder.anticipated_start_date).toBe(''); + expect(emptyWorkOrder.anticipated_end_date).toBe(''); + expect(emptyWorkOrder.labels).toEqual([]); + }); + + test('jsonApiMapping should return correct mapping structure', () => { + const mapping = newWorkOrder.jsonApiMapping(); + + expect(mapping.attributes).toContain('name'); + expect(mapping.attributes).toContain('code'); + expect(mapping.attributes).toContain('description'); + expect(mapping.attributes).toContain('anticipated_start_date'); + expect(mapping.attributes).toContain('anticipated_end_date'); + expect(mapping.attributes).toContain('labels'); + }); + + test('should have correct static relationship definitions', () => { + expect(WorkOrder.relationships).toEqual([ + { + name: 'operations', + type: 'array', + modelType: 'operations', + }, + { + name: 'template', + type: 'single', + modelType: 'work-order-templates', + }, + ]); + }); +}); \ No newline at end of file diff --git a/tests/models/WorkOrderTemplate.test.ts b/tests/models/WorkOrderTemplate.test.ts new file mode 100644 index 0000000..e68c716 --- /dev/null +++ b/tests/models/WorkOrderTemplate.test.ts @@ -0,0 +1,90 @@ +import { describe, expect, test, beforeEach } from "bun:test"; +import { WorkOrderTemplate } from "@models/WorkOrderTemplate"; +import { JsonApiSerializer } from '../../src/utils/JsonSerializer'; +import { Hydrator } from '../../src/utils/Hydrator'; + +describe('WorkOrderTemplate Model', () => { + const newId = 'b9cbbac7-65aa-40d1-aed8-f68b71aa6b6e'; + const newWorkOrderTemplateData = { + name: 'Maintenance Template', + labels: ['routine', 'maintenance'] + }; + + let newWorkOrderTemplate; + let serializer; + + beforeEach(() => { + newWorkOrderTemplate = new WorkOrderTemplate(newWorkOrderTemplateData); + const hydrator = new Hydrator(); + serializer = new JsonApiSerializer(hydrator.getModelMap()); + }); + + const verifyPayloadStructure = (payload, includeId = false) => { + const expectedPayload = { + data: { + type: "work-order-templates", + attributes: { + name: newWorkOrderTemplateData.name, + labels: newWorkOrderTemplateData.labels + }, + relationships: {} + } + }; + + if (includeId) { + expectedPayload.data['id'] = newId; + } + + expect(payload).toEqual(expectedPayload); + }; + + test('newly created model should have correct attributes', () => { + expect(newWorkOrderTemplate.type).toBe('work-order-templates'); + expect(newWorkOrderTemplate.name).toBe(newWorkOrderTemplateData.name); + expect(newWorkOrderTemplate.labels).toEqual(newWorkOrderTemplateData.labels); + }); + + test('create payload should have correct attributes and relationships', () => { + const payload = serializer.buildCreatePayload(newWorkOrderTemplate); + verifyPayloadStructure(payload); + }); + + test('patch payload should have correct attributes and relationships', () => { + newWorkOrderTemplate.id = newId; + const payload = serializer.buildUpdatePayload(newWorkOrderTemplate); + verifyPayloadStructure(payload, true); + }); + + test('should handle input data with attributes structure', () => { + const dataWithAttributes = { + attributes: { + name: 'Repair Template', + labels: ['urgent', 'repair'] + } + }; + + const workOrderTemplate = new WorkOrderTemplate(dataWithAttributes); + + expect(workOrderTemplate.name).toBe(dataWithAttributes.attributes.name); + expect(workOrderTemplate.labels).toEqual(dataWithAttributes.attributes.labels); + }); + + test('should handle missing or empty data', () => { + const emptyWorkOrderTemplate = new WorkOrderTemplate(); + + expect(emptyWorkOrderTemplate.type).toBe('work-order-templates'); + expect(emptyWorkOrderTemplate.name).toBe(''); + expect(emptyWorkOrderTemplate.labels).toEqual([]); + }); + + test('jsonApiMapping should return correct mapping structure', () => { + const mapping = newWorkOrderTemplate.jsonApiMapping(); + + expect(mapping.attributes).toContain('name'); + expect(mapping.attributes).toContain('labels'); + }); + + test('should have correct static relationship definitions', () => { + expect(WorkOrderTemplate.relationships).toEqual([]); + }); +}); \ No newline at end of file