426e9eeabd
Voice Workbench / headless workbench (mocked backends) (push) Has been cancelled
Voice Workbench / real acoustic lane (nightly, provisioned only) (push) Has been cancelled
ci / test (push) Has been cancelled
ci / lint-and-format (push) Has been cancelled
ci / build (push) Has been cancelled
ci / dev-startup (push) Has been cancelled
gitleaks / gitleaks (push) Has been cancelled
Markdown Links / Relative Markdown Links (push) Has been cancelled
Quality (Extended) / Homepage Build (PR smoke) (push) Has been cancelled
Quality (Extended) / Comment-only diff guard (push) Has been cancelled
Quality (Extended) / Format + Type Safety Ratchet (push) Has been cancelled
Quality (Extended) / Develop Gate (secret scan + UI determinism) (push) Has been cancelled
Quality (Extended) / Develop Gate (lint) (push) Has been cancelled
Chat shell gestures / Chat shell gesture + parity e2e (push) Has been cancelled
Cloud Gateway Discord / Test (push) Has been cancelled
Benchmark Bridge Tests / benchmark (bunx @biomejs/biome check packages/lifeops-bench/src, benchmark-lint) (push) Has been cancelled
Benchmark Bridge Tests / benchmark (bunx vitest run --config packages/lifeops-bench/vitest.config.ts --root packages/lifeops-bench --passWithNoTests, benchmark-tests) (push) Has been cancelled
Build Agent Image / build-and-push (push) Has been cancelled
Dev Smoke / bun run dev onboarding chat (push) Has been cancelled
Dev Smoke / Vite HMR dependency-level smoke (push) Has been cancelled
Electrobun Submodule Guard / electrobun gitlink is fetchable (push) Has been cancelled
Publish @elizaos/example-code / check_npm (push) Has been cancelled
Publish @elizaos/example-code / publish_npm (push) Has been cancelled
Publish @elizaos/plugin-elizacloud / verify_version (push) Has been cancelled
Publish @elizaos/plugin-elizacloud / publish_npm (push) Has been cancelled
Sandbox Live Smoke / Sandbox live smoke (push) Has been cancelled
Snap Build & Test / Build Snap (amd64) (push) Has been cancelled
Snap Build & Test / Build Snap (arm64) (push) Has been cancelled
Test Packaging / elizaos CLI global-install smoke (node + bun) (push) Has been cancelled
Cloud Gateway Webhook / Test (push) Has been cancelled
Cloud Tests / lint-and-types (push) Has been cancelled
Cloud Tests / unit-tests (push) Has been cancelled
Cloud Tests / integration-tests (push) Has been cancelled
Cloud Tests / e2e-tests (push) Has been cancelled
CodeQL Advanced / Analyze (javascript-typescript) (push) Has been cancelled
Deploy Apps Worker (Product 2) / Determine environment (push) Has been cancelled
Deploy Apps Worker (Product 2) / Deploy apps worker to apps-control host (${{ needs.determine-env.outputs.environment }}) (push) Has been cancelled
Deploy Eliza Provisioning Worker / Determine environment (push) Has been cancelled
Deploy Eliza Provisioning Worker / Deploy worker to Hetzner host (${{ needs.determine-env.outputs.environment }} @ ${{ needs.determine-env.outputs.deployment_sha }}) (push) Has been cancelled
Dev Smoke / Classify changed paths (push) Has been cancelled
supply-chain / sbom (push) Has been cancelled
supply-chain / vulnerability-scan (push) Has been cancelled
Build, Push & Deploy to Phala Cloud / build-and-push (push) Has been cancelled
Test Packaging / Validate Packaging Configs (push) Has been cancelled
Test Packaging / Build & Test PyPI Package (push) Has been cancelled
Test Packaging / PyPI on Python ${{ matrix.python }} (push) Has been cancelled
Test Packaging / Pack & Test JS Tarballs (push) Has been cancelled
UI Fixture E2E / ui-fixture-e2e (push) Has been cancelled
UI Fixture E2E / fixture-e2e (push) Has been cancelled
UI Story Gate / story-gate (push) Has been cancelled
vault-ci / test (macos-latest) (push) Has been cancelled
vault-ci / test (ubuntu-latest) (push) Has been cancelled
vault-ci / test (windows-latest) (push) Has been cancelled
vault-ci / app-core wiring tests (push) Has been cancelled
verify-patches / verify patches/CHECKSUMS.sha256 (push) Has been cancelled
Voice Benchmark Smoke / voice-emotion fixture smoke (push) Has been cancelled
Voice Benchmark Smoke / voiceagentbench fixture smoke (push) Has been cancelled
Voice Benchmark Smoke / voicebench-quality unit smoke (push) Has been cancelled
Voice Benchmark Smoke / voicebench TypeScript unit (no audio) (push) Has been cancelled
Voice Benchmark Smoke / voice bench smoke summary (push) Has been cancelled
Windows CI / windows ([bun run --cwd packages/app-core test bun run --cwd packages/elizaos test bun run --cwd packages/cloud/shared test], app-and-cli) (push) Has been cancelled
Windows CI / windows ([bun run --cwd packages/scenario-runner test bun run --cwd packages/vault test bun run --cwd packages/security test bun run --cwd plugins/plugin-coding-tools test], framework-packages) (push) Has been cancelled
Windows CI / windows ([bun run --cwd plugins/plugin-elizacloud test bun run --cwd plugins/plugin-discord test bun run --cwd plugins/plugin-anthropic test bun run --cwd plugins/plugin-openai test bun run --cwd plugins/plugin-app-control test bun run --cwd plugins/pl… (push) Has been cancelled
Windows CI / windows ([node packages/scripts/run-turbo.mjs run build --filter=@elizaos/core --filter=@elizaos/shared --filter=@elizaos/agent --concurrency=4 node packages/scripts/run-bash-linux-only.mjs scripts/verify-riscv64-buildpaths.sh node packages/scripts/run… (push) Has been cancelled
Windows CI / windows ([node packages/scripts/run-turbo.mjs run typecheck --filter=@elizaos/core --filter=@elizaos/shared --filter=@elizaos/cloud-shared --concurrency=4 bun run --cwd packages/core test bun run --cwd packages/shared test], core-runtime, 75) (push) Has been cancelled
278 lines
9.9 KiB
TypeScript
278 lines
9.9 KiB
TypeScript
/**
|
|
* External-API contract test for the plugin-owned Google -> LifeOps parser.
|
|
*
|
|
* `lifeOpsCalendarEventFromGoogle` / `lifeOpsCalendarAttendeeFromGoogle` /
|
|
* `lifeOpsCalendarSummaryFromGoogle` (src/internal/google-delegates.ts) are the
|
|
* genuine external-API boundary for the calendar view's data: they map the
|
|
* `GoogleCalendarEvent` / `GoogleCalendarListEntry` shapes produced by
|
|
* `@elizaos/plugin-google`'s `mapEvent` / list mapper (over the googleapis
|
|
* `calendar_v3` JSON) into the `LifeOpsCalendarEvent` / `LifeOpsCalendarSummary`
|
|
* the CalendarSection feed renders.
|
|
*
|
|
* The fixtures below are hand-built to match the EXACT shape `mapEvent` emits
|
|
* (verified against plugins/plugin-google/src/calendar.ts `mapEvent`, ~205-243,
|
|
* and `mapCalendarListEntry`, ~186-203):
|
|
* - timed event: start/end are ISO `new Date(dateTime).toISOString()` strings,
|
|
* `isAllDay: false`, `timeZone` populated, `htmlLink`, `meetLink` (from
|
|
* `hangoutLink` or `conferenceData.entryPoints[0].uri`), `attendees` mapped
|
|
* to `{ email, name? }`, `organizer` `{ email, name?, self }`, and a
|
|
* `metadata` object with `iCalUID`/`recurringEventId`/`createdAt`/`updatedAt`.
|
|
* - all-day event: `start`/`end` are `new Date("YYYY-MM-DDT00:00:00.000Z")`
|
|
* ISO strings, `isAllDay: true`.
|
|
* - minimal event: only `id`/`calendarId` (every other field undefined), to
|
|
* exercise the parser's `(untitled)` / `confirmed` / `false` / `null`
|
|
* defaults.
|
|
*
|
|
* This validates the parser against the real provider shape entirely within
|
|
* plugin-calendar (no @elizaos/agent / connector graph needed).
|
|
*/
|
|
|
|
import type {
|
|
GoogleCalendarEvent,
|
|
GoogleCalendarListEntry,
|
|
} from "@elizaos/plugin-google";
|
|
import type { LifeOpsConnectorGrant } from "@elizaos/shared";
|
|
import { describe, expect, it } from "vitest";
|
|
|
|
import {
|
|
lifeOpsCalendarEventFromGoogle,
|
|
lifeOpsCalendarSummaryFromGoogle,
|
|
} from "../src/internal/google-delegates.js";
|
|
|
|
const AGENT_ID = "agent-7";
|
|
|
|
const ownerGrant: LifeOpsConnectorGrant = {
|
|
id: "connector-account:acct-123",
|
|
agentId: AGENT_ID,
|
|
provider: "google",
|
|
connectorAccountId: "acct-123",
|
|
side: "owner",
|
|
identity: { email: "owner@example.com" },
|
|
identityEmail: "owner@example.com",
|
|
grantedScopes: ["https://www.googleapis.com/auth/calendar.events"],
|
|
capabilities: ["google.calendar.read", "google.calendar.write"],
|
|
tokenRef: null,
|
|
mode: "local",
|
|
executionTarget: "local",
|
|
sourceOfTruth: "connector_account",
|
|
preferredByAgent: true,
|
|
cloudConnectionId: null,
|
|
metadata: {},
|
|
lastRefreshAt: "2026-06-16T00:00:00.000Z",
|
|
createdAt: "2026-06-01T00:00:00.000Z",
|
|
updatedAt: "2026-06-16T00:00:00.000Z",
|
|
};
|
|
|
|
// Shape produced by plugin-google `mapEvent` for a normal timed event.
|
|
const timedGoogleEvent: GoogleCalendarEvent = {
|
|
id: "evt_abc123",
|
|
calendarId: "owner@example.com",
|
|
title: "Design sync",
|
|
status: "confirmed",
|
|
start: new Date("2026-06-17T15:00:00.000Z").toISOString(),
|
|
end: new Date("2026-06-17T16:00:00.000Z").toISOString(),
|
|
isAllDay: false,
|
|
timeZone: "America/New_York",
|
|
htmlLink: "https://www.google.com/calendar/event?eid=evt_abc123",
|
|
meetLink: "https://meet.google.com/abc-defg-hij",
|
|
attendees: [
|
|
{ email: "owner@example.com", name: "Owner Person" },
|
|
{ email: "guest@elsewhere.com", name: "Guest" },
|
|
],
|
|
location: "Room 4B",
|
|
description: "Weekly design review",
|
|
organizer: { email: "owner@example.com", name: "Owner Person", self: true },
|
|
metadata: {
|
|
iCalUID: "evt_abc123@google.com",
|
|
recurringEventId: null,
|
|
createdAt: "2026-06-10T09:00:00.000Z",
|
|
updatedAt: "2026-06-15T12:00:00.000Z",
|
|
},
|
|
};
|
|
|
|
// Shape produced by `mapEvent` for an all-day event (date-only start/end).
|
|
const allDayGoogleEvent: GoogleCalendarEvent = {
|
|
id: "evt_holiday",
|
|
calendarId: "owner@example.com",
|
|
title: "Company holiday",
|
|
status: "confirmed",
|
|
start: new Date("2026-07-04T00:00:00.000Z").toISOString(),
|
|
end: new Date("2026-07-05T00:00:00.000Z").toISOString(),
|
|
isAllDay: true,
|
|
timeZone: "America/New_York",
|
|
htmlLink: "https://www.google.com/calendar/event?eid=evt_holiday",
|
|
attendees: [],
|
|
metadata: {
|
|
iCalUID: "evt_holiday@google.com",
|
|
recurringEventId: null,
|
|
createdAt: null,
|
|
updatedAt: null,
|
|
},
|
|
};
|
|
|
|
// Minimal shape — `mapEvent` only guarantees id + calendarId; everything else
|
|
// can be undefined (e.g. a cancelled or sparse event row).
|
|
const minimalGoogleEvent: GoogleCalendarEvent = {
|
|
id: "evt_min",
|
|
calendarId: "primary",
|
|
};
|
|
|
|
describe("lifeOpsCalendarEventFromGoogle (Google -> LifeOps contract)", () => {
|
|
it("maps a timed event to a contract-valid LifeOpsCalendarEvent", () => {
|
|
const result = lifeOpsCalendarEventFromGoogle({
|
|
event: timedGoogleEvent,
|
|
grant: ownerGrant,
|
|
agentId: AGENT_ID,
|
|
syncedAt: "2026-06-16T08:00:00.000Z",
|
|
});
|
|
|
|
expect(result.id).toBe(
|
|
"agent-7:google:owner:calendar:owner@example.com:evt_abc123",
|
|
);
|
|
expect(result.externalId).toBe("evt_abc123");
|
|
expect(result.agentId).toBe(AGENT_ID);
|
|
expect(result.provider).toBe("google");
|
|
expect(result.side).toBe("owner");
|
|
expect(result.calendarId).toBe("owner@example.com");
|
|
expect(result.title).toBe("Design sync");
|
|
expect(result.description).toBe("Weekly design review");
|
|
expect(result.location).toBe("Room 4B");
|
|
expect(result.status).toBe("confirmed");
|
|
expect(result.startAt).toBe("2026-06-17T15:00:00.000Z");
|
|
expect(result.endAt).toBe("2026-06-17T16:00:00.000Z");
|
|
expect(result.isAllDay).toBe(false);
|
|
expect(result.timezone).toBe("America/New_York");
|
|
expect(result.htmlLink).toBe(
|
|
"https://www.google.com/calendar/event?eid=evt_abc123",
|
|
);
|
|
// conferenceLink is sourced from the Google `meetLink` field.
|
|
expect(result.conferenceLink).toBe("https://meet.google.com/abc-defg-hij");
|
|
expect(result.organizer).toEqual({
|
|
email: "owner@example.com",
|
|
name: "Owner Person",
|
|
self: true,
|
|
});
|
|
|
|
// attendees mapped to the LifeOps attendee shape (response/self/organizer
|
|
// default false/null because Google's summary projection omits them).
|
|
expect(result.attendees).toEqual([
|
|
{
|
|
email: "owner@example.com",
|
|
displayName: "Owner Person",
|
|
responseStatus: null,
|
|
self: false,
|
|
organizer: false,
|
|
optional: false,
|
|
},
|
|
{
|
|
email: "guest@elsewhere.com",
|
|
displayName: "Guest",
|
|
responseStatus: null,
|
|
self: false,
|
|
organizer: false,
|
|
optional: false,
|
|
},
|
|
]);
|
|
|
|
// metadata is merged with googlePlugin:true plus the Google metadata block.
|
|
expect(result.metadata.googlePlugin).toBe(true);
|
|
expect(result.metadata.iCalUID).toBe("evt_abc123@google.com");
|
|
expect(result.metadata.updatedAt).toBe("2026-06-15T12:00:00.000Z");
|
|
|
|
expect(result.syncedAt).toBe("2026-06-16T08:00:00.000Z");
|
|
expect(result.updatedAt).toBe("2026-06-16T08:00:00.000Z");
|
|
expect(result.connectorAccountId).toBe("acct-123");
|
|
expect(result.grantId).toBe("connector-account:acct-123");
|
|
expect(result.accountEmail).toBe("owner@example.com");
|
|
});
|
|
|
|
it("maps an all-day event preserving date-only bounds and isAllDay", () => {
|
|
const result = lifeOpsCalendarEventFromGoogle({
|
|
event: allDayGoogleEvent,
|
|
grant: ownerGrant,
|
|
agentId: AGENT_ID,
|
|
syncedAt: "2026-06-16T08:00:00.000Z",
|
|
});
|
|
|
|
expect(result.isAllDay).toBe(true);
|
|
expect(result.startAt).toBe("2026-07-04T00:00:00.000Z");
|
|
expect(result.endAt).toBe("2026-07-05T00:00:00.000Z");
|
|
expect(result.title).toBe("Company holiday");
|
|
// No conference / no attendees for this event.
|
|
expect(result.conferenceLink).toBeNull();
|
|
expect(result.attendees).toEqual([]);
|
|
});
|
|
|
|
it("applies (untitled)/confirmed/false/null defaults for a minimal event", () => {
|
|
const result = lifeOpsCalendarEventFromGoogle({
|
|
event: minimalGoogleEvent,
|
|
grant: ownerGrant,
|
|
agentId: AGENT_ID,
|
|
syncedAt: "2026-06-16T08:00:00.000Z",
|
|
});
|
|
|
|
expect(result.title).toBe("(untitled)");
|
|
expect(result.description).toBe("");
|
|
expect(result.location).toBe("");
|
|
expect(result.status).toBe("confirmed");
|
|
expect(result.isAllDay).toBe(false);
|
|
expect(result.timezone).toBeNull();
|
|
expect(result.htmlLink).toBeNull();
|
|
expect(result.conferenceLink).toBeNull();
|
|
expect(result.organizer).toBeNull();
|
|
expect(result.attendees).toEqual([]);
|
|
// start falls back to syncedAt, end falls back to start when absent.
|
|
expect(result.startAt).toBe("2026-06-16T08:00:00.000Z");
|
|
expect(result.endAt).toBe("2026-06-16T08:00:00.000Z");
|
|
expect(result.id).toBe("agent-7:google:owner:calendar:primary:evt_min");
|
|
});
|
|
});
|
|
|
|
describe("lifeOpsCalendarSummaryFromGoogle (calendar list contract)", () => {
|
|
const listEntry: GoogleCalendarListEntry = {
|
|
calendarId: "owner@example.com",
|
|
summary: "Owner Person",
|
|
description: "Primary calendar",
|
|
primary: true,
|
|
accessRole: "owner",
|
|
backgroundColor: "#0b8043",
|
|
foregroundColor: "#ffffff",
|
|
timeZone: "America/New_York",
|
|
selected: true,
|
|
};
|
|
|
|
it("maps a calendar list entry to a LifeOpsCalendarSummary with feed defaults", () => {
|
|
const summary = lifeOpsCalendarSummaryFromGoogle({
|
|
entry: listEntry,
|
|
grant: ownerGrant,
|
|
});
|
|
|
|
expect(summary).toEqual({
|
|
provider: "google",
|
|
side: "owner",
|
|
grantId: "connector-account:acct-123",
|
|
accountEmail: "owner@example.com",
|
|
calendarId: "owner@example.com",
|
|
summary: "Owner Person",
|
|
description: "Primary calendar",
|
|
primary: true,
|
|
accessRole: "owner",
|
|
backgroundColor: "#0b8043",
|
|
foregroundColor: "#ffffff",
|
|
timeZone: "America/New_York",
|
|
selected: true,
|
|
// includeInFeed defaults to true (opt-out, never opt-in).
|
|
includeInFeed: true,
|
|
});
|
|
});
|
|
|
|
it("honors an explicit includeInFeed=false override", () => {
|
|
const summary = lifeOpsCalendarSummaryFromGoogle({
|
|
entry: listEntry,
|
|
grant: ownerGrant,
|
|
includeInFeed: false,
|
|
});
|
|
expect(summary.includeInFeed).toBe(false);
|
|
});
|
|
});
|