Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 34 additions & 13 deletions src/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ export interface Appliance {
*/
export interface PropertyApplianceRequest {
property_appliance: {
inspection_id?: number | null;
inspection_id?: string | null;
serial: string | null;
model: string | null;
year: string | null;
Expand Down Expand Up @@ -133,7 +133,7 @@ interface JsonApiErrorResponse {

/**
* Client for interacting with the Fixle API
*
*
* @example
* const client = new FixleClient({
* apiUrl: 'https://api.fixle.com',
Expand All @@ -144,7 +144,7 @@ interface JsonApiErrorResponse {
* const propertyId = await client.findOrCreateProperty('123 Main St, Portland, OR 97201');
*
* // Create an inspection
* await client.createInspection(propertyId, 12345);
* await client.createInspection(propertyId, 'inspection-slug-123');
*
* // Add an appliance
* await client.createAppliance(propertyId, {
Expand Down Expand Up @@ -257,7 +257,7 @@ export class FixleClient {
* { email: 'buyer@example.com', name: 'John Doe' }
* ]);
*/
async findOrCreateProperty(address: string, buyers?: Contact[]): Promise<number> {
async findOrCreateProperty(address: string, buyers?: Contact[]): Promise<string> {
const parts = address.split(',').map(s => s.trim());
const streetAddress = parts[0] || address;
const cityStateZip = parts[1] || '';
Expand All @@ -276,7 +276,7 @@ export class FixleClient {
};

const response = await this.makeApiRequest('POST', '/api/v1/properties', propertyData);
return parseInt(response.data?.id || response.id);
return response.data?.id || response.id;
}

/**
Expand All @@ -289,22 +289,22 @@ export class FixleClient {
* @throws Error if the API request fails or the property doesn't exist
*
* @example
* const fixleInspectionId = await client.createInspection(123, 45678);
* const fixleInspectionId = await client.createInspection('6b1d7345-b8a5-4536-bc3d-a0dd72aafa75', 'ed4cbaf3-d0dc-4100-b970-0a1a22157e70');
* console.log(`Created inspection with Fixle ID: ${fixleInspectionId}`);
*
* @example
* // With inspector info and sellers
* const fixleInspectionId = await client.createInspection(123, 45678, {
* const fixleInspectionId = await client.createInspection('6b1d7345-b8a5-4536-bc3d-a0dd72aafa75', 'ed4cbaf3-d0dc-4100-b970-0a1a22157e70', {
* inspectorImageUrl: 'https://example.com/inspector.jpg',
* inspectorName: 'John Inspector',
* inspectorEmail: 'inspector@example.com',
* sellers: [{ email: 'seller@example.com', name: 'Jane Seller' }]
* });
*/
async createInspection(propertyId: number, externalInspectionId: number, options?: CreateInspectionOptions): Promise<number> {
async createInspection(propertyId: string, externalInspectionId: string, options?: CreateInspectionOptions): Promise<string> {
const inspectionData: InspectionRequest = {
inspection: {
external_id: externalInspectionId.toString(),
external_id: externalInspectionId,
...(options?.inspectorImageUrl && { inspector_image_url: options.inspectorImageUrl }),
...(options?.inspectorName && { inspector_name: options.inspectorName }),
...(options?.inspectorEmail && { inspector_email: options.inspectorEmail }),
Expand All @@ -313,7 +313,7 @@ export class FixleClient {
};

const response = await this.makeApiRequest('POST', `/api/v1/properties/${propertyId}/inspections`, inspectionData);
return parseInt(response.data?.id || response.id);
return response.data?.id || response.id;
}

/**
Expand All @@ -329,7 +329,7 @@ export class FixleClient {
* @throws Error if the API request fails or the property doesn't exist
*
* @example
* await client.createAppliance(123, {
* await client.createAppliance('6b1d7345-b8a5-4536-bc3d-a0dd72aafa75', {
* item_name: 'Water Heater',
* section_name: 'Basement',
* brand: 'Rheem',
Expand All @@ -339,7 +339,7 @@ export class FixleClient {
* year: '2020'
* }, 45678);
*/
async createAppliance(propertyId: number, appliance: Appliance, inspectionId?: number): Promise<void> {
async createAppliance(propertyId: string, appliance: Appliance, inspectionId?: string): Promise<void> {
const applianceData: PropertyApplianceRequest = {
property_appliance: {
...(inspectionId !== undefined && { inspection_id: inspectionId }),
Expand All @@ -354,6 +354,27 @@ export class FixleClient {
await this.makeApiRequest('POST', `/api/v1/properties/${propertyId}/appliances`, applianceData);
}


/**
* Creates report records for an inspection
*
* Each report is associated with a single inspection
*
* @param reportExternalIds - External IDs of the reports to add to the inspection
* @param inspectionId - Fixle inspection ID to link the reports to
* @returns Promise that resolves when the reports are created
* @throws Error if the API request fails or the inspection doesn't exist
*
* @example
* await client.createReports(['75ba1b64-c25b-48a2-a86c-46865f8301bb'], '4f6f3501-a9d1-4139-b5ef-a0dd4f33c6e4');
*/
async createReports(reportExternalIds: string[], inspectionId: string): Promise<void> {
await this.makeApiRequest('POST', `/api/v1/reports/bulk_create`, {
inspection_id: inspectionId,
external_ids: reportExternalIds,
});
}

/**
* Triggers the appliance report generation for an inspection
*
Expand All @@ -373,7 +394,7 @@ export class FixleClient {
* console.log(`Report status: ${result.status}, message: ${result.message}`);
*/
async triggerApplianceReport(
inspectionId: number,
inspectionId: string,
reportId?: string,
async: boolean = true
): Promise<{ status: string; message: string }> {
Expand Down
44 changes: 23 additions & 21 deletions tests/client.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,42 +53,44 @@ describe('FixleClient', () => {
setupHttpMock('{"data":{"id":"123"}}');

const propertyId = await client.findOrCreateProperty('123 Main St, Portland, OR 97201');
expect(propertyId).toBe(123);
expect(propertyId).toBe('123');
});
});

describe('createInspection', () => {
it('should create inspection without inspectorImageUrl and return Fixle ID', async () => {
setupHttpMock('{"data":{"id":"456"}}');

const fixleInspectionId = await client.createInspection(123, 45678);
const fixleInspectionId = await client.createInspection('123', 'slug-45678');

expect(fixleInspectionId).toBe(456);
expect(fixleInspectionId).toBe('456');
const parsedBody = JSON.parse(capturedBody);
expect(parsedBody.inspection.external_id).toBe('45678');
expect(parsedBody.inspection.external_id).toBe('slug-45678');
expect(parsedBody.inspection.inspector_image_url).toBeUndefined();
});

it('should create inspection with inspectorImageUrl and return Fixle ID', async () => {
setupHttpMock('{"data":{"id":"456"}}');

const fixleInspectionId = await client.createInspection(123, 45678, 'https://example.com/inspector.jpg');
const fixleInspectionId = await client.createInspection('123', 'slug-45678', {
inspectorImageUrl: 'https://example.com/inspector.jpg',
});

expect(fixleInspectionId).toBe(456);
expect(fixleInspectionId).toBe('456');
const parsedBody = JSON.parse(capturedBody);
expect(parsedBody.inspection.external_id).toBe('45678');
expect(parsedBody.inspection.external_id).toBe('slug-45678');
expect(parsedBody.inspection.inspector_image_url).toBe('https://example.com/inspector.jpg');
});

it('should include empty string inspectorImageUrl when explicitly provided', async () => {
it('should not include inspectorImageUrl when not provided in options', async () => {
setupHttpMock('{"data":{"id":"456"}}');

const fixleInspectionId = await client.createInspection(123, 45678, '');
const fixleInspectionId = await client.createInspection('123', 'slug-45678', {});

expect(fixleInspectionId).toBe(456);
expect(fixleInspectionId).toBe('456');
const parsedBody = JSON.parse(capturedBody);
expect(parsedBody.inspection.external_id).toBe('45678');
expect(parsedBody.inspection.inspector_image_url).toBe('');
expect(parsedBody.inspection.external_id).toBe('slug-45678');
expect(parsedBody.inspection.inspector_image_url).toBeUndefined();
});
});

Expand All @@ -106,7 +108,7 @@ describe('FixleClient', () => {
it('should create appliance without inspectionId', async () => {
setupHttpMock('{"data":{"id":"789"}}');

await client.createAppliance(123, testAppliance);
await client.createAppliance('123', testAppliance);

const parsedBody = JSON.parse(capturedBody);
expect(parsedBody.property_appliance.model).toBe('XE50M06ST45U1');
Expand All @@ -119,18 +121,18 @@ describe('FixleClient', () => {
it('should create appliance with inspectionId', async () => {
setupHttpMock('{"data":{"id":"789"}}');

await client.createAppliance(123, testAppliance, 45678);
await client.createAppliance('123', testAppliance, '45678');

const parsedBody = JSON.parse(capturedBody);
expect(parsedBody.property_appliance.model).toBe('XE50M06ST45U1');
expect(parsedBody.property_appliance.serial).toBe('ABC123456');
expect(parsedBody.property_appliance.inspection_id).toBe(45678);
expect(parsedBody.property_appliance.inspection_id).toBe('45678');
});

it('should include notes with item_name and brand', async () => {
setupHttpMock('{"data":{"id":"789"}}');

await client.createAppliance(123, testAppliance);
await client.createAppliance('123', testAppliance);

const parsedBody = JSON.parse(capturedBody);
expect(parsedBody.property_appliance.notes).toBe('Water Heater - Brand: Rheem');
Expand All @@ -139,7 +141,7 @@ describe('FixleClient', () => {
it('should include location from section_name', async () => {
setupHttpMock('{"data":{"id":"789"}}');

await client.createAppliance(123, testAppliance);
await client.createAppliance('123', testAppliance);

const parsedBody = JSON.parse(capturedBody);
expect(parsedBody.property_appliance.location).toBe('Basement');
Expand All @@ -150,7 +152,7 @@ describe('FixleClient', () => {
it('should trigger appliance report with default async=true', async () => {
setupHttpMock('{"data":{"id":"456","attributes":{"status":"pending","message":"Report queued"}}}', 200);

const result = await client.triggerApplianceReport(456);
const result = await client.triggerApplianceReport('456');

expect(result.status).toBe('pending');
expect(result.message).toBe('Report queued');
Expand All @@ -159,7 +161,7 @@ describe('FixleClient', () => {
it('should trigger appliance report with reportId', async () => {
setupHttpMock('{"data":{"id":"456","attributes":{"status":"pending","message":"Report queued"}}}', 200);

const result = await client.triggerApplianceReport(456, 'report-123');
const result = await client.triggerApplianceReport('456', 'report-123');

expect(result.status).toBe('pending');
expect(result.message).toBe('Report queued');
Expand All @@ -168,7 +170,7 @@ describe('FixleClient', () => {
it('should trigger appliance report with async=false', async () => {
setupHttpMock('{"data":{"id":"456","attributes":{"status":"completed","message":"Report generated"}}}', 200);

const result = await client.triggerApplianceReport(456, 'report-123', false);
const result = await client.triggerApplianceReport('456', 'report-123', false);

expect(result.status).toBe('completed');
expect(result.message).toBe('Report generated');
Expand All @@ -177,7 +179,7 @@ describe('FixleClient', () => {
it('should return default values when attributes are missing', async () => {
setupHttpMock('{"data":{"id":"456"}}', 200);

const result = await client.triggerApplianceReport(456);
const result = await client.triggerApplianceReport('456');

expect(result.status).toBe('unknown');
expect(result.message).toBe('Report triggered');
Expand Down