Initial Commit

This commit is contained in:
2026-05-27 23:29:53 +01:00
commit fbc70f96e6
10000 changed files with 1102152 additions and 0 deletions

View File

@@ -0,0 +1,476 @@
import { describe, expect, it } from "@suaveplan/testing/runner";
import { MacOSContactsError } from "../error-codes/error-codes.js";
import type { AppleScriptRunner } from "../executor/executor.js";
import { MacOSContactsBridge } from "./bridge.js";
function fixtureContact(id: string, first: string, last: string): string {
return [
`${id}|||${first}|||${last}|||Org||| ||| |||false`,
`PHONES:mobile:+1555${id.padStart(7, "0")};;;`,
`EMAILS:work:${first.toLowerCase()}@test.com;;;`,
"ADDRESSES:",
].join("\n");
}
const singleFixture = fixtureContact("c1", "Alice", "Smith");
const multiFixture = [
fixtureContact("c1", "Alice", "Smith"),
"---CONTACT---",
fixtureContact("c2", "Bob", "Jones"),
].join("\n");
const groupsFixture = ["g1|||Family|||3", "g2|||Coworkers|||8"].join("\n");
function recordingRunner(behaviour: Record<string, string | Error>): {
runner: AppleScriptRunner;
calls: string[];
} {
const calls: string[] = [];
return {
calls,
runner: {
async run(script: string): Promise<string> {
calls.push(script);
for (const [key, value] of Object.entries(behaviour)) {
if (script.includes(key)) {
if (value instanceof Error) throw value;
return value;
}
}
throw new Error(`Unexpected script: ${script}`);
},
},
};
}
function alwaysFailingRunner(message: string): AppleScriptRunner {
return {
async run(): Promise<string> {
throw new Error(message);
},
};
}
describe("MacOSContactsBridge", () => {
it("constructor defaults to the platform runner", { timeout: 5000 }, () => {
const bridge = new MacOSContactsBridge({ timeoutMs: 5000 });
expect(bridge).toBeInstanceOf(MacOSContactsBridge);
});
it("constructor accepts an injected runner", { timeout: 5000 }, () => {
const { runner } = recordingRunner({});
const bridge = new MacOSContactsBridge({}, { runner });
expect(bridge).toBeInstanceOf(MacOSContactsBridge);
});
it("constructor accepts no arguments", { timeout: 5000 }, () => {
const bridge = new MacOSContactsBridge();
expect(bridge).toBeInstanceOf(MacOSContactsBridge);
});
it("sync uses Date.now() as the default clock", {
timeout: 5000,
}, async () => {
const { runner } = recordingRunner({ "every person": multiFixture });
const before = Date.now();
const bridge = new MacOSContactsBridge({}, { runner });
const result = await bridge.sync();
const after = Date.now();
expect(result.syncedAt.getTime()).toBeGreaterThanOrEqual(before);
expect(result.syncedAt.getTime()).toBeLessThanOrEqual(after);
});
it("constructor rejects invalid timeoutMs", { timeout: 5000 }, () => {
let caught: unknown;
try {
new MacOSContactsBridge({ timeoutMs: -1 });
} catch (e) {
caught = e;
}
expect(caught).toBeInstanceOf(MacOSContactsError);
expect((caught as MacOSContactsError).code).toBe(
"MACOS_CONTACTS.INVALID_INPUT"
);
});
it("getAllContacts parses contacts from the runner", {
timeout: 5000,
}, async () => {
const { runner } = recordingRunner({ "every person": multiFixture });
const bridge = new MacOSContactsBridge({}, { runner });
const contacts = await bridge.getAllContacts();
expect(contacts).toHaveLength(2);
expect(contacts[0]?.firstName).toBe("Alice");
});
it("getAllContacts returns empty array when runner returns empty stdout", {
timeout: 5000,
}, async () => {
const { runner } = recordingRunner({ "every person": "" });
const bridge = new MacOSContactsBridge({}, { runner });
expect(await bridge.getAllContacts()).toEqual([]);
});
it("getAllContacts maps permission-denial errors", {
timeout: 5000,
}, async () => {
const runner = alwaysFailingRunner("not authorized");
const bridge = new MacOSContactsBridge({}, { runner });
let caught: unknown;
try {
await bridge.getAllContacts();
} catch (e) {
caught = e;
}
expect(caught).toBeInstanceOf(MacOSContactsError);
expect((caught as MacOSContactsError).code).toBe(
"MACOS_CONTACTS.CONTACTS_PERMISSION_DENIED"
);
});
it("getAllContacts maps generic errors to access-failed", {
timeout: 5000,
}, async () => {
const runner = alwaysFailingRunner("boom");
const bridge = new MacOSContactsBridge({}, { runner });
let caught: unknown;
try {
await bridge.getAllContacts();
} catch (e) {
caught = e;
}
expect((caught as MacOSContactsError).code).toBe(
"MACOS_CONTACTS.CONTACTS_ACCESS_FAILED"
);
});
it("getAllContacts handles non-Error rejections from the runner", {
timeout: 5000,
}, async () => {
const runner: AppleScriptRunner = {
async run(): Promise<string> {
const failure: unknown = "literal";
throw failure;
},
};
const bridge = new MacOSContactsBridge({}, { runner });
let caught: unknown;
try {
await bridge.getAllContacts();
} catch (e) {
caught = e;
}
expect((caught as MacOSContactsError).code).toBe(
"MACOS_CONTACTS.CONTACTS_ACCESS_FAILED"
);
expect((caught as MacOSContactsError).cause).toBeUndefined();
});
it("searchContacts parses contacts on a non-empty query", {
timeout: 5000,
}, async () => {
const { runner } = recordingRunner({
"name contains": singleFixture,
});
const bridge = new MacOSContactsBridge({}, { runner });
const contacts = await bridge.searchContacts("Alice");
expect(contacts).toHaveLength(1);
});
it("searchContacts short-circuits on empty query", {
timeout: 5000,
}, async () => {
const { runner, calls } = recordingRunner({});
const bridge = new MacOSContactsBridge({}, { runner });
expect(await bridge.searchContacts("")).toEqual([]);
expect(await bridge.searchContacts(" ")).toEqual([]);
expect(calls).toHaveLength(0);
});
it("searchContacts attaches the query discriminant on failure", {
timeout: 5000,
}, async () => {
const runner = alwaysFailingRunner("boom");
const bridge = new MacOSContactsBridge({}, { runner });
let caught: unknown;
try {
await bridge.searchContacts("Alice");
} catch (e) {
caught = e;
}
expect((caught as MacOSContactsError).query).toBe("Alice");
});
it("getContactById returns the matching contact", {
timeout: 5000,
}, async () => {
const { runner } = recordingRunner({ "every person": multiFixture });
const bridge = new MacOSContactsBridge({}, { runner });
const c = await bridge.getContactById("c2");
expect(c?.firstName).toBe("Bob");
});
it("getContactById returns null when no contact matches", {
timeout: 5000,
}, async () => {
const { runner } = recordingRunner({ "every person": singleFixture });
const bridge = new MacOSContactsBridge({}, { runner });
expect(await bridge.getContactById("not-real")).toBeNull();
});
it("getContactById short-circuits on empty id", {
timeout: 5000,
}, async () => {
const { runner, calls } = recordingRunner({});
const bridge = new MacOSContactsBridge({}, { runner });
expect(await bridge.getContactById("")).toBeNull();
expect(calls).toHaveLength(0);
});
it("getContactById attaches the contactId discriminant on access failure", {
timeout: 5000,
}, async () => {
const runner = alwaysFailingRunner("boom");
const bridge = new MacOSContactsBridge({}, { runner });
let caught: unknown;
try {
await bridge.getContactById("c1");
} catch (e) {
caught = e;
}
expect((caught as MacOSContactsError).contactId).toBe("c1");
});
it("getContactById records permission_denied outcome on permission failure", {
timeout: 5000,
}, async () => {
const runner = alwaysFailingRunner("not authorized");
const bridge = new MacOSContactsBridge({}, { runner });
let caught: unknown;
try {
await bridge.getContactById("c1");
} catch (e) {
caught = e;
}
expect((caught as MacOSContactsError).code).toBe(
"MACOS_CONTACTS.CONTACTS_PERMISSION_DENIED"
);
expect((caught as MacOSContactsError).contactId).toBe("c1");
});
it("getContactById rewraps MacOSContactsError with the contactId discriminant", {
timeout: 5000,
}, async () => {
const runner: AppleScriptRunner = {
async run(): Promise<string> {
throw new MacOSContactsError(
"MACOS_CONTACTS.CONTACTS_ACCESS_FAILED",
"inner"
);
},
};
const bridge = new MacOSContactsBridge({}, { runner });
let caught: unknown;
try {
await bridge.getContactById("c1");
} catch (e) {
caught = e;
}
expect((caught as MacOSContactsError).contactId).toBe("c1");
});
it("getGroups parses groups from the runner", {
timeout: 5000,
}, async () => {
const { runner } = recordingRunner({ "every group": groupsFixture });
const bridge = new MacOSContactsBridge({}, { runner });
const groups = await bridge.getGroups();
expect(groups).toHaveLength(2);
expect(groups[1]?.memberCount).toBe(8);
});
it("getGroups maps runner failures to access-failed", {
timeout: 5000,
}, async () => {
const runner = alwaysFailingRunner("boom");
const bridge = new MacOSContactsBridge({}, { runner });
let caught: unknown;
try {
await bridge.getGroups();
} catch (e) {
caught = e;
}
expect((caught as MacOSContactsError).code).toBe(
"MACOS_CONTACTS.CONTACTS_ACCESS_FAILED"
);
});
it("getGroupMembers parses the named group", {
timeout: 5000,
}, async () => {
const { runner } = recordingRunner({
"people of targetGroup": singleFixture,
});
const bridge = new MacOSContactsBridge({}, { runner });
const members = await bridge.getGroupMembers("Family");
expect(members).toHaveLength(1);
});
it("getGroupMembers short-circuits on empty group name", {
timeout: 5000,
}, async () => {
const { runner, calls } = recordingRunner({});
const bridge = new MacOSContactsBridge({}, { runner });
expect(await bridge.getGroupMembers("")).toEqual([]);
expect(await bridge.getGroupMembers(" ")).toEqual([]);
expect(calls).toHaveLength(0);
});
it("getGroupMembers attaches the groupName discriminant on failure", {
timeout: 5000,
}, async () => {
const runner = alwaysFailingRunner("boom");
const bridge = new MacOSContactsBridge({}, { runner });
let caught: unknown;
try {
await bridge.getGroupMembers("Family");
} catch (e) {
caught = e;
}
expect((caught as MacOSContactsError).groupName).toBe("Family");
});
it("sync returns the contacts on success", { timeout: 5000 }, async () => {
const { runner } = recordingRunner({ "every person": multiFixture });
const bridge = new MacOSContactsBridge({}, { runner });
const result = await bridge.sync();
expect(result.contacts).toHaveLength(2);
expect(result.total).toBe(2);
expect(result.errors).toEqual([]);
});
it("sync captures errors instead of throwing", {
timeout: 5000,
}, async () => {
const runner = alwaysFailingRunner("boom — generic failure");
const bridge = new MacOSContactsBridge({}, { runner });
const result = await bridge.sync();
expect(result.contacts).toEqual([]);
expect(result.total).toBe(0);
expect(result.errors).toHaveLength(1);
expect(result.errors[0]?.code).toBe(
"MACOS_CONTACTS.CONTACTS_ACCESS_FAILED"
);
expect(result.errors[0]?.message).toContain("boom");
});
it("sync records the permission-denied code on permission failure", {
timeout: 5000,
}, async () => {
const runner = alwaysFailingRunner("not authorized");
const bridge = new MacOSContactsBridge({}, { runner });
const result = await bridge.sync();
expect(result.errors[0]?.code).toBe(
"MACOS_CONTACTS.CONTACTS_PERMISSION_DENIED"
);
});
it("sync surfaces a generic message for non-Error rejections", {
timeout: 5000,
}, async () => {
const runner: AppleScriptRunner = {
async run(): Promise<string> {
const failure: unknown = "literal";
throw failure;
},
};
const bridge = new MacOSContactsBridge({}, { runner });
const result = await bridge.sync();
expect(result.errors[0]?.message).toContain(
"Failed to invoke Contacts.app"
);
});
it("sync uses the injected clock for syncedAt", {
timeout: 5000,
}, async () => {
const { runner } = recordingRunner({ "every person": multiFixture });
const bridge = new MacOSContactsBridge(
{},
{ runner, clock: () => 1_700_000_000_000 }
);
const result = await bridge.sync();
expect(result.syncedAt.getTime()).toBe(1_700_000_000_000);
});
it("getAllContacts maps not-found patterns to CONTACTS_NOT_FOUND", {
timeout: 5000,
}, async () => {
const runner = alwaysFailingRunner("can't get person id 123");
const bridge = new MacOSContactsBridge({}, { runner });
let caught: unknown;
try {
await bridge.getAllContacts();
} catch (e) {
caught = e;
}
expect((caught as MacOSContactsError).code).toBe(
"MACOS_CONTACTS.CONTACTS_NOT_FOUND"
);
});
it("getAllContacts maps syntax-error output to CONTACTS_PARSE_FAILED", {
timeout: 5000,
}, async () => {
const runner = alwaysFailingRunner("syntax error in script");
const bridge = new MacOSContactsBridge({}, { runner });
let caught: unknown;
try {
await bridge.getAllContacts();
} catch (e) {
caught = e;
}
expect((caught as MacOSContactsError).code).toBe(
"MACOS_CONTACTS.CONTACTS_PARSE_FAILED"
);
});
it("getAllContacts maps missing-binary failures to CONTACTS_UNAVAILABLE", {
timeout: 5000,
}, async () => {
const runner = alwaysFailingRunner("ENOENT no such file or directory");
const bridge = new MacOSContactsBridge({}, { runner });
let caught: unknown;
try {
await bridge.getAllContacts();
} catch (e) {
caught = e;
}
expect((caught as MacOSContactsError).code).toBe(
"MACOS_CONTACTS.CONTACTS_UNAVAILABLE"
);
});
it("isAvailable returns true when the runner resolves", {
timeout: 5000,
}, async () => {
const { runner } = recordingRunner({ "System Events": "Finder" });
const bridge = new MacOSContactsBridge({}, { runner });
expect(await bridge.isAvailable()).toBe(true);
});
it("isAvailable returns false when the runner resolves with empty stdout", {
timeout: 5000,
}, async () => {
const { runner } = recordingRunner({ "System Events": "" });
const bridge = new MacOSContactsBridge({}, { runner });
expect(await bridge.isAvailable()).toBe(false);
});
it("isAvailable returns false when the runner rejects", {
timeout: 5000,
}, async () => {
const runner = alwaysFailingRunner("command not found");
const bridge = new MacOSContactsBridge({}, { runner });
expect(await bridge.isAvailable()).toBe(false);
});
});