From 19eea9cba8a189a776b18ef212dd9b78d6cdaf59 Mon Sep 17 00:00:00 2001 From: HirenGajjar Date: Tue, 12 May 2026 10:31:47 -0700 Subject: [PATCH] feat(storage): implement geo field validation in BaseStorage.validateField --- src/storage/base-storage.ts | 39 +++++++++- tests/unit/storage/base-storage-geo.test.ts | 82 +++++++++++++++++++++ 2 files changed, 118 insertions(+), 3 deletions(-) create mode 100644 tests/unit/storage/base-storage-geo.test.ts diff --git a/src/storage/base-storage.ts b/src/storage/base-storage.ts index b7cce11..66aa849 100644 --- a/src/storage/base-storage.ts +++ b/src/storage/base-storage.ts @@ -183,10 +183,43 @@ export abstract class BaseStorage { } break; - case FieldType.GEO: - // TODO: Implement geo validation - // Geo fields should be in format "longitude,latitude" + case FieldType.GEO: { + if (typeof value !== 'string') { + throw new SchemaValidationError( + `Field '${fieldName}' should be a string in "longitude,latitude" format but got: ${typeof value}` + ); + } + + const parts = value.trim().split(','); + + if (parts.length !== 2) { + throw new SchemaValidationError( + `Field '${fieldName}' should be in "longitude,latitude" format but got: "${value}"` + ); + } + + const longitude = parseFloat(parts[0].trim()); + const latitude = parseFloat(parts[1].trim()); + + if (isNaN(longitude) || isNaN(latitude)) { + throw new SchemaValidationError( + `Field '${fieldName}' contains invalid coordinates. Both longitude and latitude must be valid numbers. Got: "${value}"` + ); + } + + if (longitude < -180 || longitude > 180) { + throw new SchemaValidationError( + `Field '${fieldName}' longitude must be between -180 and 180, got: ${longitude}` + ); + } + + if (latitude < -90 || latitude > 90) { + throw new SchemaValidationError( + `Field '${fieldName}' latitude must be between -90 and 90, got: ${latitude}` + ); + } break; + } } } diff --git a/tests/unit/storage/base-storage-geo.test.ts b/tests/unit/storage/base-storage-geo.test.ts new file mode 100644 index 0000000..00a8e10 --- /dev/null +++ b/tests/unit/storage/base-storage-geo.test.ts @@ -0,0 +1,82 @@ +import { describe, it, expect } from 'vitest'; +import { IndexSchema, IndexInfo } from '../../../src/schema/schema.js'; +import { BaseStorage } from '../../../src/storage/base-storage.js'; +import { SchemaValidationError } from '../../../src/errors.js'; + +// Test double — exposes protected validate() for unit testing +class TestStorage extends BaseStorage { + public testValidate(doc: Record) { + return this.validate(doc); + } + async write() { + return []; + } + async get() { + return []; + } +} + +// Minimal schema with a geo field +function makeGeoSchema() { + const index = new IndexInfo({ name: 'test-geo-index' }); + const schema = new IndexSchema({ index }); + schema.addField({ name: 'location', type: 'geo' }); + return schema; +} + +describe('BaseStorage geo field validation', () => { + describe('valid geo values', () => { + it('should accept "0,0"', () => { + const storage = new TestStorage(makeGeoSchema()); + expect(() => storage.testValidate({ location: '0,0' })).not.toThrow(); + }); + + it('should accept boundary values "-180,-90"', () => { + const storage = new TestStorage(makeGeoSchema()); + expect(() => storage.testValidate({ location: '-180,-90' })).not.toThrow(); + }); + + it('should accept boundary values "180,90"', () => { + const storage = new TestStorage(makeGeoSchema()); + expect(() => storage.testValidate({ location: '180,90' })).not.toThrow(); + }); + + it('should accept realistic coordinates', () => { + const storage = new TestStorage(makeGeoSchema()); + expect(() => storage.testValidate({ location: '40.7128,-74.0060' })).not.toThrow(); + }); + }); + + describe('invalid geo values', () => { + it('should reject a non-string value', () => { + const storage = new TestStorage(makeGeoSchema()); + expect(() => storage.testValidate({ location: 12345 })).toThrow(SchemaValidationError); + }); + + it('should reject a value with no comma', () => { + const storage = new TestStorage(makeGeoSchema()); + expect(() => storage.testValidate({ location: '12345' })).toThrow( + SchemaValidationError + ); + }); + + it('should reject non-numeric parts', () => { + const storage = new TestStorage(makeGeoSchema()); + expect(() => storage.testValidate({ location: 'abc,def' })).toThrow( + SchemaValidationError + ); + }); + + it('should reject longitude out of range', () => { + const storage = new TestStorage(makeGeoSchema()); + expect(() => storage.testValidate({ location: '181,0' })).toThrow( + SchemaValidationError + ); + }); + + it('should reject latitude out of range', () => { + const storage = new TestStorage(makeGeoSchema()); + expect(() => storage.testValidate({ location: '0,91' })).toThrow(SchemaValidationError); + }); + }); +});