Identity
Gmail, Calendar, Drive
Gmail, Google Calendar and Google Drive for a signed-in user, with push notifications and history that a test can expire.
The Google emulator keeps a mailbox, a calendar and a drive for every seeded account. Your app
reaches them with the token it got at sign-in, through googleapis or
plain REST, and your test reads and changes them as if from elsewhere.
Set up
Seed each account's data by email, next to the OAuth client:
import { appEnvironment, defineConfig, googleService } from "@danwo/sdk"
const url = "http://localhost:3000"
const ada = "ada@example.com"
export default defineConfig({
testDir: "tests",
readiness: { mode: "page+backend", settle: true },
environment: () =>
appEnvironment({
app: { command: "npm run dev", url },
services: [
googleService({
seed: {
oauth_clients: [
{
client_id: "danwo-client.apps.googleusercontent.com",
client_secret: "danwo-client-secret",
name: "My app",
redirect_uris: [`${url}/auth/google/callback`],
},
],
users: [{ email: ada, name: "Ada Lovelace" }],
gmail: {
[ada]: [
{ from: "billing@example.org", to: ada, subject: "Invoice 42", text: "Due Friday" },
],
},
calendar: {
[ada]: [
{
summary: "Design review",
start: { dateTime: "2026-11-02T10:00:00Z" },
end: { dateTime: "2026-11-02T11:00:00Z" },
},
],
},
drive: { [ada]: [{ name: "notes.txt", content: "First draft" }] },
pubsub: [
{
subscription: "projects/my-app/subscriptions/gmail",
topic: "projects/my-app/topics/gmail",
endpoint: `${url}/api/google/push`,
},
],
},
}),
],
}),
})Seed it
| Field | What it seeds |
|---|---|
gmail | Messages already in each mailbox, with labels and dates. |
calendar | Events on each primary calendar, in the Calendar API's own event shape. |
drive | Files in each drive, with content and folders. |
pubsub | Push subscriptions: what Gmail publishes to a topic after users.watch is POSTed to the endpoint. |
Use it in a test
Deliver a new email, as if someone wrote in, and check your app filed it:
import { type GoogleFixtures, gmailMailbox, test as base } from "@danwo/sdk"
const test = base.extend<GoogleFixtures>()
test("files a new invoice email", async ({ app, screen, expect, settle, google }) => {
const mailbox = gmailMailbox(google, "ada@example.com")
mailbox.deliver({ from: "billing@example.org", to: "ada@example.com", subject: "Invoice 43" })
await settle()
await app.open("/invoices")
await expect(screen.getByText("Invoice 43")).toBeVisible()
expect(mailbox.sent()).toHaveLength(0)
})deliver sends the push notification too, so an app that watches the mailbox hears of it.
| Helper | Reads | Changes |
|---|---|---|
gmailMailbox(google, email) | messages(label), inbox(), sent() | deliver(mail), expireHistory() |
googleCalendar(google, email) | events() | add(event), cancel(id), expireSyncTokens() |
googleDrive(google, email) | files(), read(id) | add(file), remove(id) |
The two expire helpers make the next incremental sync fail, as Google does after a while, so
you can test that your app falls back to a full sync. gmailMail(google) gives the mail to the
mail fixture.
What's covered
| Area | Covered |
|---|---|
| Gmail | Messages with search, sending, labels, threads, drafts, attachments, history, and push with watch. |
| Calendar | Calendars, events with recurrence, quick add, free/busy, sync tokens and watch. |
| Drive | Files with queries, uploads, copies, permissions, changes and watch. |
Faults
| Call | Faults the matrix tries |
|---|---|
A Gmail send, such as POST …/messages/send | 429, 403, 500, 503, a 10-second delay, connection reset, answer lost after Gmail sent it |
A read, such as GET …/history or Calendar's events | 429, 403, 500, 503, a 10-second delay, connection reset, and the expiry answer: 404 for Gmail history, 410 for a Calendar sync token |
| Every push notification | arrives twice, arrives 10 minutes late, forged, 1 hour stale |
Webhooks
Gmail pushes go to the Pub/Sub subscription's endpoint, in Pub/Sub's push format, and can be
signed with an OIDC token for a service account, as an authenticated subscription is. Calendar
and Drive watch channels POST to their address with Google's X-Goog-* headers.
Fidelity
Not checked live. These calls need a signed-in Google account, so only the sign-in endpoints are compared; see Google.
Not covered yet
- Sheets, Docs and Slides
- Shared drives
- Gmail settings, filters and delegates