feature: initial commit

This commit is contained in:
Andy Burke 2025-06-13 20:40:28 -07:00
commit ce024ba87a
17 changed files with 1141 additions and 0 deletions

View file

@ -0,0 +1,13 @@
import * as asserts from '@std/assert';
Deno.test({
name: 'import fsdb',
permissions: {
env: true
},
fn: async () => {
const fsdb = await import('../fsdb.ts');
asserts.assert(fsdb);
}
});

View file

@ -0,0 +1,39 @@
import * as asserts from '@std/assert';
import * as fsdb from '../fsdb.ts';
import { get_data_dir } from './helpers.ts';
Deno.test({
name: 'store and retrieve an item',
permissions: {
env: true,
read: ['./'],
write: ['./data']
},
fn: async () => {
type ITEM = {
id: string;
value: string;
};
const items: fsdb.FSDB_COLLECTION<ITEM> = new fsdb.FSDB_COLLECTION<ITEM>({
name: 'test-02-items',
root: get_data_dir() + '/test-02-items'
});
asserts.assert(items);
const item = {
id: 'blah-fish-test-with-cozy-home-down-here-yall-work',
value: 'the blah fish test, of course'
};
const stored_item = await items.create(item);
asserts.assertObjectMatch(stored_item, item);
const fetched_item = await items.get(item.id);
asserts.assert(fetched_item);
asserts.assertObjectMatch(fetched_item, stored_item);
}
});

View file

@ -0,0 +1,88 @@
import * as asserts from '@std/assert';
import * as fsdb from '../fsdb.ts';
import { FSDB_INDEXER_SYMLINKS } from '../indexers/symlinks.ts';
import { get_data_dir, random_email_address, random_phone_number } from './helpers.ts';
import lurid from '@andyburke/lurid';
import by_email from '../organizers/by_email.ts';
import by_character from '../organizers/by_character.ts';
import by_phone from '../organizers/by_phone.ts';
import { sentence } from 'jsr:@ndaidong/txtgen';
Deno.test({
name: 'index some items',
permissions: {
env: true,
// https://github.com/denoland/deno/discussions/17258
read: true,
write: true
},
fn: async () => {
type ITEM = {
id: string;
email: string;
phone: string;
value: string;
};
const item_collection: fsdb.FSDB_COLLECTION<ITEM> = new fsdb.FSDB_COLLECTION<ITEM>({
name: 'test-03-items',
root: get_data_dir() + '/test-03-items',
indexers: {
email: new FSDB_INDEXER_SYMLINKS<ITEM>({
name: 'email',
field: 'email',
organize: by_email
}),
phone: new FSDB_INDEXER_SYMLINKS<ITEM>({
name: 'phone',
field: 'phone',
organize: by_phone
}),
by_character_test: new FSDB_INDEXER_SYMLINKS<ITEM>({
name: 'by_character_test',
organize: by_character,
get_values_to_index: (item: ITEM) => item.value.split(/\W/).filter((word) => word.length > 3),
to_many: true
})
}
});
asserts.assert(item_collection);
const items: ITEM[] = [];
for (let i = 0; i < 10; ++i) {
const item = {
id: lurid(),
email: random_email_address(),
phone: random_phone_number(),
value: sentence()
};
items.push(item);
const stored_item: ITEM = await item_collection.create(item);
asserts.assertObjectMatch(stored_item, item);
}
for (const item of items) {
const fetched_by_email: ITEM[] = await item_collection.find({ email: item.email });
asserts.assertLess(fetched_by_email.length, items.length);
asserts.assertGreater(fetched_by_email.length, 0);
asserts.assert(fetched_by_email.find((email_item) => email_item.id === item.id));
const fetched_by_phone: ITEM[] = await item_collection.find({ phone: item.phone });
asserts.assertLess(fetched_by_phone.length, items.length);
asserts.assertGreater(fetched_by_phone.length, 0);
asserts.assert(fetched_by_phone.find((phone_item) => phone_item.id === item.id));
const words_in_value: string[] = item.value.split(/\W/).filter((word) => word.length > 3);
const random_word_in_value: string = words_in_value[Math.floor(Math.random() * words_in_value.length)];
const fetched_by_word_in_value: ITEM[] = await item_collection.find({ by_character_test: random_word_in_value });
asserts.assertLess(fetched_by_word_in_value.length, items.length);
asserts.assertGreater(fetched_by_word_in_value.length, 0);
asserts.assert(fetched_by_word_in_value.find((word_in_value_item) => word_in_value_item.id === item.id));
}
}
});

View file

@ -0,0 +1,104 @@
import * as asserts from '@std/assert';
import * as fsdb from '../fsdb.ts';
import { FSDB_INDEXER_SYMLINKS } from '../indexers/symlinks.ts';
import { get_data_dir, random_email_address, random_phone_number } from './helpers.ts';
import lurid from '@andyburke/lurid';
import by_email from '../organizers/by_email.ts';
import by_character from '../organizers/by_character.ts';
import by_phone from '../organizers/by_phone.ts';
import { sentence } from 'jsr:@ndaidong/txtgen';
Deno.test({
name: 'index some items',
permissions: {
env: true,
// https://github.com/denoland/deno/discussions/17258
read: true,
write: true
},
fn: async () => {
type ITEM = {
id: string;
email: string;
phone: string;
value: string;
};
const item_collection: fsdb.FSDB_COLLECTION<ITEM> = new fsdb.FSDB_COLLECTION<ITEM>({
name: 'test-04-items',
root: get_data_dir() + '/test-04-items',
indexers: {
email: new FSDB_INDEXER_SYMLINKS<ITEM>({
name: 'email',
field: 'email',
organize: by_email
}),
phone: new FSDB_INDEXER_SYMLINKS<ITEM>({
name: 'phone',
field: 'phone',
organize: by_phone
}),
by_character_test: new FSDB_INDEXER_SYMLINKS<ITEM>({
name: 'by_character_test',
organize: by_character,
get_values_to_index: (item: ITEM) => item.value.split(/\W/).filter((word) => word.length > 3),
to_many: true
})
}
});
asserts.assert(item_collection);
const items: ITEM[] = [];
for (let i = 0; i < 10; ++i) {
const item = {
id: lurid(),
email: random_email_address(),
phone: random_phone_number(),
value: sentence()
};
items.push(item);
const stored_item: ITEM = await item_collection.create(item);
asserts.assertObjectMatch(stored_item, item);
}
for (const item of items) {
const fetched_by_email: ITEM[] = await item_collection.find({ email: item.email });
asserts.assertLess(fetched_by_email.length, items.length);
asserts.assertGreater(fetched_by_email.length, 0);
asserts.assert(fetched_by_email.find((email_item) => email_item.id === item.id));
const fetched_by_phone: ITEM[] = await item_collection.find({ phone: item.phone });
asserts.assertLess(fetched_by_phone.length, items.length);
asserts.assertGreater(fetched_by_phone.length, 0);
asserts.assert(fetched_by_phone.find((phone_item) => phone_item.id === item.id));
const words_in_value: string[] = item.value.split(/\W/).filter((word) => word.length > 3);
const random_word_in_value: string = words_in_value[Math.floor(Math.random() * words_in_value.length)];
const fetched_by_word_in_value: ITEM[] = await item_collection.find({ by_character_test: random_word_in_value });
asserts.assertLess(fetched_by_word_in_value.length, items.length);
asserts.assertGreater(fetched_by_word_in_value.length, 0);
asserts.assert(fetched_by_word_in_value.find((word_in_value_item) => word_in_value_item.id === item.id));
}
// leave one item behind so the whole db for this test doesn't get cleaned up so I can hand-review it
for (const item of items.slice(1)) {
await item_collection.delete(item);
const fetched_by_email: ITEM[] = await item_collection.find({ email: item.email });
asserts.assertFalse(fetched_by_email.find((email_item) => email_item.id === item.id));
const fetched_by_phone: ITEM[] = await item_collection.find({ phone: item.phone });
asserts.assertFalse(fetched_by_phone.find((phone_item) => phone_item.id === item.id));
const words_in_value: string[] = item.value.split(/\W/).filter((word) => word.length > 3);
const random_word_in_value: string = words_in_value[Math.floor(Math.random() * words_in_value.length)];
const fetched_by_word_in_value: ITEM[] = await item_collection.find({ by_character_test: random_word_in_value });
asserts.assertFalse(fetched_by_word_in_value.find((word_in_value_item) => word_in_value_item.id === item.id));
}
}
});

53
tests/helpers.ts Normal file
View file

@ -0,0 +1,53 @@
import lurid from '@andyburke/lurid';
import { convert_to_words } from '@andyburke/lurid/word_bytes';
const TLDs: string[] = [
'com',
'org',
'net',
'edu',
'gov',
'nexus',
'shop',
'unreasonablylongtldname'
];
const random_byte_buffer: Uint8Array = new Uint8Array(3);
export function random_email_address(): string {
crypto.getRandomValues(random_byte_buffer);
const name = convert_to_words(random_byte_buffer).join('-');
crypto.getRandomValues(random_byte_buffer);
const domain = convert_to_words(random_byte_buffer).join('-');
const tld = TLDs[Math.floor(Math.random() * TLDs.length)];
return `${name}@${domain}.${tld}`;
}
export function random_username(): string {
crypto.getRandomValues(random_byte_buffer);
return convert_to_words(random_byte_buffer).join('-');
}
function get_a_random_array_element(values: any[]): any {
return values[Math.floor(Math.random() * values.length)];
}
const country_codes: string[] = ['', '+1', '1', '01', '219', '40', '506', '999'];
const digits: number[] = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
const joinings: string[] = [' ', '-', '.'];
export function random_phone_number(): string {
const country_code = get_a_random_array_element(country_codes);
const area_code = [0, 0, 0].map((_) => get_a_random_array_element(digits)).join('');
const central_office_code = [0, 0, 0].map((_) => get_a_random_array_element(digits)).join('');
const subscriber_code = [0, 0, 0, 0].map((_) => get_a_random_array_element(digits)).join('');
return `${country_code}${country_code ? get_a_random_array_element(joinings) : ''}${area_code}${
get_a_random_array_element(joinings)
}${central_office_code}${get_a_random_array_element(joinings)}${subscriber_code}`;
}
const DATA_DIR = lurid();
export function get_data_dir(): string {
return Deno.env.get('TEST_DATA_STORAGE_ROOT') ?? DATA_DIR;
}