Identity
Microsoft
Sign in with Microsoft, then Microsoft Graph's Outlook mail, calendars, OneDrive and change notifications for each seeded user.
danwo's Microsoft emulator answers login.microsoftonline.com for sign-in and tokens, and
graph.microsoft.com for Outlook mail, calendars, OneDrive and subscriptions. MSAL and the
Graph client work against it unchanged.
Set up
Add microsoftService with your app registration and the users who may sign in:
import { appEnvironment, defineConfig, microsoftService } from "@danwo/sdk"
const url = "http://localhost:3000"
const app = {
client_id: "00000000-0000-0000-0000-00000000a001",
client_secret: "danwo-client-secret",
name: "My app",
redirect_uris: [`${url}/auth/microsoft/callback`],
}
export default defineConfig({
testDir: "tests",
readiness: { mode: "page+backend", settle: true },
environment: () =>
appEnvironment({
app: {
command: "npm run dev",
url,
env: { AZURE_AD_CLIENT_ID: app.client_id, AZURE_AD_CLIENT_SECRET: app.client_secret },
},
services: [
microsoftService({
seed: {
oauth_clients: [app],
users: [{ email: "[email protected]", name: "Ada Lovelace" }],
mail: [
{ user: "[email protected]", from: "[email protected]", subject: "Quarterly plan" },
],
events: [
{
user: "[email protected]",
subject: "Planning",
start: "2026-11-02T10:00:00",
end: "2026-11-02T11:00:00",
},
],
files: [{ user: "[email protected]", path: "/Documents/plan.txt", content: "Draft" }],
},
}),
],
}),
})Seed it
| Field | What it seeds |
|---|---|
users | Work or school accounts by email, with names and a tenant. |
oauth_clients | App registrations, with redirect URIs and an optional single tenant. |
mail | Messages in a user's folders, Inbox by default. |
events | Calendar events, with time zones, attendees, all-day and free/busy. |
files | OneDrive files and folders, by path. |
Use it in a test
Sign in, then check the app sent mail from the user's mailbox:
import { type MicrosoftFixtures, outlookMailbox, test as base } from "@danwo/sdk"
const test = base.extend<MicrosoftFixtures>()
test("sends the weekly summary from Outlook", async ({ app, screen, expect, microsoft }) => {
await app.open("/")
await screen.getByRole("button", { name: "Sign in with Microsoft" }).click()
await screen.getByRole("button", { name: "Continue as Ada Lovelace" }).click()
await screen.getByRole("button", { name: "Send summary" }).click()
await expect(screen.getByText("Summary sent")).toBeVisible()
const sent = outlookMailbox(microsoft, "[email protected]").latest("Sent Items")
expect(sent?.subject).toBe("Your weekly summary")
})| Helper | What it's for |
|---|---|
outlookMailbox(microsoft, email) | Read a user's mail by folder, and deliver new mail. |
outlookCalendar(microsoft, email) | Read events in a range, and add one. |
oneDrive(microsoft, email) | List, read, write and remove files. |
graphSubscriptions(microsoft) | The change subscriptions apps made, live or expired. |
microsoftSignIns(microsoft) | Who signed in to which app. |
revokeMicrosoftSessions(microsoft, email) | Sign the user out everywhere, so refreshes fail. |
outlookMail(microsoft) gives the mail to the mail fixture.
What's covered
| Area | Covered |
|---|---|
| Sign-in | The account chooser, tokens, refresh, logout, OpenID discovery and keys. |
| Messages, folders, sending, drafts, replies and forwards, delta sync. | |
| Queries | Filters with plain comparisons, ordering, paging and selected fields. |
| Calendar | Events, calendar views, recurrence, time zones and delta sync. |
| OneDrive | Items by id or path, uploads and upload sessions, downloads, delta. |
| Subscriptions | Change notifications, with validation and your clientState. |
| Users | The signed-in user, and revoking their sessions. |
Faults
| Call | Faults the matrix tries |
|---|---|
POST …/oauth2/v2.0/token | 500, 503, 400, a 10-second delay, connection reset, answer lost after Microsoft issued the token |
A Graph write, such as POST /me/sendMail | 429, 503, 504, a 10-second delay, connection reset, answer lost after Graph acted |
| A Graph read | 429, 503, 504, a 10-second delay, connection reset |
| Every change notification | arrives twice, arrives 10 minutes late, forged, 1 hour stale |
Webhooks
Change notifications go to each subscription's notificationUrl, after Graph's validation
handshake, and carry the clientState the app chose, so your app can refuse a forged one.
Fidelity
Public endpoints only. danwo fidelity microsoft compares the discovery document and the
signing keys with Microsoft's. Both match. Graph calls need a signed-in account, so they aren't
checked live.
Not covered yet
- Teams, SharePoint sites and Planner
- Personal Microsoft accounts' consumer-only features
- Batch requests (
$batch) and$filterbeyond plain comparisons