Connect your app
Point your app's OAuth and Google Calendar SDK clients at Calendarpit while keeping production Google settings switchable.
Add a provider switch
Your app must choose Calendarpit explicitly. Starting Calendarpit does not intercept network traffic to Google.
To use Calendarpit in local development or automated tests, add a provider switch. Put it in the code that creates your OAuth client and Google Calendar SDK client.
Environment variable names such as CALENDARPIT_URL are conventions in your app. Google SDKs do not read CALENDARPIT_URL automatically. Your initialization code must read your configuration and pass the local URLs to the SDK.
Keep your provider credentials separate:
- Use Calendarpit access tokens and refresh tokens only with the local provider.
- Keep real Google client secrets and tokens in your Google configuration.
Never put real Google credentials in a fixture.
Override every provider endpoint
Override all five endpoints together. Changing only the Calendar API URL is not enough.
If OAuth stays on its default endpoints, your app still sends these requests to Google:
- Browser redirects
- Token exchanges
- Token refreshes
| Purpose | Local endpoint |
|---|---|
| Authorization screen | http://127.0.0.1:4173/oauth/authorize |
| Code exchange and refresh | http://127.0.0.1:4173/oauth/token |
| User profile | http://127.0.0.1:4173/userinfo |
| Calendar API root | http://127.0.0.1:4173/ |
| Token revocation | http://127.0.0.1:4173/oauth/revoke |
Calendar API URL format by SDK
Each SDK expects a different form of the Calendar API URL:
- JavaScript (
@googleapis/calendar): Appends/calendar/v3/...torootUrl. Do not include/calendar/v3twice. - Python, Go, and .NET: Expect the full service URL, including
/calendar/v3/. - Java: Sets the root URL and service path separately.
See SDK examples.
Watch callback address
A watch callback address points in the opposite direction: from Calendarpit to your app. An example is http://127.0.0.1:4175/notifications.
By default, Calendarpit allows only loopback callback hosts (127.0.0.1, localhost, [::1]). To allow extra hosts, list them in CALENDARPIT_WATCH_HOSTS. See Watch notifications.
Register a test OAuth client
Register your app in the clients array of your fixture. Calendarpit checks these values:
- Authorization endpoint:
client_idandredirect_uri. /oauth/token:client_idandclient_secret, plusredirect_urifor code exchange.
{
"clients": [
{
"id": "my-app",
"secret": "local-test-secret",
"redirectUris": ["http://127.0.0.1:3000/auth/google/callback"]
}
]
}Two fields need care:
redirectUrismust contain your app's exact callback URL. Calendarpit matchesredirect_uriby exact string equality. It does not apply port wildcards or trailing-slash normalization.secretis required. Your app must send it asclient_secret(or HTTP Basic) on every/oauth/tokenrequest, including PKCE flows.
How the fixture is applied
A fixture replaces the whole workspace. Include users and calendars alongside clients. If you use the sample app, keep the sample-app entry.
When the fixture takes effect depends on how you pass it:
- Startup fixture: Applies only to a new database or a
:memory:session. - Running or persisted workspace: Load the fixture with
POST /__calendarpit/fixtures, or start with a new database.
A reset restores the startup fixture, not a fixture loaded through the API. To keep your client across resets, do one of the following:
- Pass the fixture at startup (
--configorCALENDARPIT_CONFIG) with a new database or:memory:session. - Reload the fixture after each reset.
See Fixtures.
Built-in client
The built-in default fixture registers one OAuth client for local testing:
- Client ID:
sample-app - Client secret:
local-sample-secret(a public local test value) - Redirect URI:
http://127.0.0.1:4175/callback
For the full authorization code, refresh, /userinfo, and revocation flow, see Local OAuth.
JavaScript OAuth and Calendar client
With @googleapis/calendar and google-auth-library, configure two settings:
endpointsonOAuth2ClientrootUrlon the Calendar client
import { calendar } from '@googleapis/calendar';
import { OAuth2Client } from 'google-auth-library';
const origin = 'http://127.0.0.1:4173';
const auth = new OAuth2Client({
clientId: 'sample-app',
clientSecret: 'local-sample-secret',
redirectUri: 'http://127.0.0.1:4175/callback',
endpoints: {
oauth2AuthBaseUrl: `${origin}/oauth/authorize`,
oauth2TokenUrl: `${origin}/oauth/token`,
oauth2RevokeUrl: `${origin}/oauth/revoke`,
},
});
const api = calendar({ version: 'v3', auth, rootUrl: origin });
// After the callback: auth.setCredentials((await auth.getToken(code)).tokens)
const profile = await auth.request({ url: `${origin}/userinfo` });
const calendars = await api.calendarList.list();
const { data } = await api.freebusy.query({
requestBody: {
timeMin: '2026-10-19T00:00:00+09:00',
timeMax: '2026-10-20T00:00:00+09:00',
items: [{ id: 'primary' }],
},
});
console.log(data.calendars?.primary?.busy);Scopes
Authorize the grant with scopes that cover all three calls in this example. For example: auth.generateAuthUrl({ access_type: 'offline', scope: ['https://www.googleapis.com/auth/calendar', 'email', 'profile'] }).
calendarList.listrequirescalendar,calendar.readonly,calendar.calendarlist, orcalendar.calendarlist.readonly. Narrower scopes such ascalendar.eventsorcalendar.freebusydo not cover it./userinforequiresopenid,email,profile, or auserinfo.*scope. It returnsemailonly with an email scope, andnameonly with a profile scope.- Refresh tokens are issued only with
access_type=offline.
See Local OAuth for the full scope list.
Token refresh and unsupported helpers
After you call auth.setCredentials, token refresh requests from OAuth2Client also use the local oauth2TokenUrl.
Other OAuth2Client helpers, such as getTokenInfo and verifyIdToken, still call Google. This changes only if you also override tokenInfoUrl and the certificate URLs, as the sample app does.
Calendarpit does not support these endpoints or issue ID tokens. Do not call those helpers against the local provider.
For Python, Go, and other languages, see SDK examples.
Reference sample app
The repository includes an optional sample app using @googleapis/calendar and google-auth-library. It is not bundled with Calendarpit. You do not need it to connect your own app.
To try it, prepare a source checkout using the development guide. Then run these commands in separate terminals:
# Terminal 1: start the packaged server
calendarpit serve
# Terminal 2: from the prepared source checkout
bun run sample:devCalendarpit runs at http://127.0.0.1:4173, and the sample app runs at http://127.0.0.1:4175.
Patterns to reuse
The sample app is a single-connection local demo. It shows two patterns for your own integration:
- State cookie verification: It stores the OAuth
stateparameter in a 5-minute HttpOnly cookie (sample_state). It verifies the value on/callbackbefore exchanging the authorization code. - Loopback-only outbound guard: It blocks outbound SDK requests to hosts other than
127.0.0.1,localhost, or[::1]. It also rejects HTTP redirects. Together, these prevent accidental requests to real Google servers.
Environment variables
The sample app uses these environment variables:
| Environment variable | Default |
|---|---|
CALENDARPIT_URL | http://127.0.0.1:4173 |
SAMPLE_PORT | 4175 |
SAMPLE_CLIENT_ID | sample-app |
SAMPLE_CLIENT_SECRET | local-sample-secret |
AUTHORIZATION_URL | ${CALENDARPIT_URL}/oauth/authorize |
TOKEN_URL | ${CALENDARPIT_URL}/oauth/token |
USERINFO_URL | ${CALENDARPIT_URL}/userinfo |
CALENDAR_API_URL | ${CALENDARPIT_URL} |
The sample app also sets these values:
- Revocation URL:
${CALENDARPIT_URL}/oauth/revoke - Redirect URI:
http://127.0.0.1:<SAMPLE_PORT>/callback
The built-in sample-app client registers only port 4175. If you change SAMPLE_PORT, update the client's redirectUris in a fixture.
Checklist for your app
When you maintain a Calendarpit integration in your app, verify:
- All endpoint overrides: Authorization, token exchange, token refresh, user profile, Calendar API calls, and token revocation all use the local endpoints.
- Callback reachability:
- The browser can reach your app's OAuth
redirect_uri. - Calendarpit can reach your app's watch webhook address. If the webhook host is not loopback, set
CALENDARPIT_WATCH_HOSTS.
- The browser can reach your app's OAuth
- Full application flow: Test your UI and background workers across login, event reads and writes, watch notifications, token refresh, revocation, and workspace reset.
- App-specific fixtures: Keep your app's fixtures and E2E test scenarios in its own repository.