Skip to content

Add coverage to all models #69

New issue

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

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

Already on GitHub? Sign in to your account

Draft
wants to merge 1 commit into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 6 additions & 6 deletions src/models/MotRecord.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,11 +36,11 @@ export class MotRecord extends BaseModel implements Partial<JsonApiMapping> {

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 ?? '';
}
}
173 changes: 173 additions & 0 deletions tests/Client.test.ts
Original file line number Diff line number Diff line change
@@ -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();
});
});
72 changes: 72 additions & 0 deletions tests/ClientConfig.test.ts
Original file line number Diff line number Diff line change
@@ -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');
});

});
116 changes: 116 additions & 0 deletions tests/models/Appointment.test.ts
Original file line number Diff line number Diff line change
@@ -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',
},
]);
});
});
Loading