Claude
Skills
Sign in
Back

onenote-local-dev-loop

Included with Lifetime
$97 forever

Set up a local development loop for OneNote integrations with mock Graph API responses. Use when developing OneNote features without Azure credentials or to avoid rate limits during development. Trigger with "onenote local dev", "onenote mock", "onenote testing setup".

Backend & APIssaasonenotemicrosoft

What this skill does

# OneNote Local Dev Loop

## Overview

Testing OneNote integrations typically requires Azure AD credentials and live Graph API calls, which means authentication friction on every dev session and risk of hitting the 600 req/60s rate limit during rapid iteration. This skill sets up a local development loop with mock Graph responses so you can develop and test OneNote features without Azure credentials, without rate limits, and with instant feedback.

The mock layer intercepts HTTP calls to `graph.microsoft.com` and returns realistic fixture data, including the XHTML output format that differs from input format. You can switch between mock and live Graph with a single environment variable.

## Prerequisites

- Node.js 18+ or Python 3.10+
- Familiarity with your project's test framework (vitest/jest for Node, pytest for Python)
- Optional: completed `onenote-install-auth` for live mode switching

## Instructions

### Step 1: Project Structure

```
my-onenote-app/
├── .env                          # GRAPH_MODE=mock or GRAPH_MODE=live
├── .env.example                  # Template (commit this, not .env)
├── src/
│   ├── client.ts                 # Graph client factory (mock/live switching)
│   ├── onenote.ts                # Business logic (testable)
│   └── types.ts                  # OneNote type definitions
├── tests/
│   ├── fixtures/
│   │   ├── notebooks.json        # Mock notebook list response
│   │   ├── sections.json         # Mock section list response
│   │   ├── pages.json            # Mock page list response
│   │   ├── page-content.html     # Mock page HTML (output format)
│   │   └── error-responses.json  # Mock error responses for testing
│   ├── mocks/
│   │   └── graph-handlers.ts     # MSW request handlers
│   └── onenote.test.ts           # Unit tests
├── package.json
└── tsconfig.json
```

### Step 2: Mock Graph API Server (TypeScript with MSW)

[MSW (Mock Service Worker)](https://mswjs.io/) intercepts HTTP requests at the network level, so your production code does not need any changes to work with mocks.

```typescript
// tests/mocks/graph-handlers.ts
import { http, HttpResponse } from "msw";

const BASE = "https://graph.microsoft.com/v1.0";

// Import fixture data
import notebooksFixture from "../fixtures/notebooks.json";
import sectionsFixture from "../fixtures/sections.json";
import pagesFixture from "../fixtures/pages.json";
import { readFileSync } from "fs";
import { join } from "path";

const pageContentFixture = readFileSync(
  join(__dirname, "../fixtures/page-content.html"),
  "utf-8"
);

export const graphHandlers = [
  // List notebooks
  http.get(`${BASE}/me/onenote/notebooks`, () => {
    return HttpResponse.json(notebooksFixture);
  }),

  // Create notebook
  http.post(`${BASE}/me/onenote/notebooks`, async ({ request }) => {
    const body = (await request.json()) as { displayName: string };
    return HttpResponse.json(
      {
        id: `notebook-${Date.now()}`,
        displayName: body.displayName,
        createdDateTime: new Date().toISOString(),
        lastModifiedDateTime: new Date().toISOString(),
        isDefault: false,
        isShared: false,
        sectionsUrl: `${BASE}/me/onenote/notebooks/notebook-${Date.now()}/sections`,
        self: `${BASE}/me/onenote/notebooks/notebook-${Date.now()}`,
      },
      { status: 201 }
    );
  }),

  // List sections in a notebook
  http.get(`${BASE}/me/onenote/notebooks/:notebookId/sections`, () => {
    return HttpResponse.json(sectionsFixture);
  }),

  // Create section
  http.post(
    `${BASE}/me/onenote/notebooks/:notebookId/sections`,
    async ({ request }) => {
      const body = (await request.json()) as { displayName: string };
      return HttpResponse.json(
        {
          id: `section-${Date.now()}`,
          displayName: body.displayName,
          createdDateTime: new Date().toISOString(),
          pagesUrl: `${BASE}/me/onenote/sections/section-${Date.now()}/pages`,
        },
        { status: 201 }
      );
    }
  ),

  // List pages in a section
  http.get(`${BASE}/me/onenote/sections/:sectionId/pages`, () => {
    return HttpResponse.json(pagesFixture);
  }),

  // Create page (accepts HTML body)
  http.post(`${BASE}/me/onenote/sections/:sectionId/pages`, async ({ request }) => {
    const html = await request.text();
    const titleMatch = html.match(/<title>(.*?)<\/title>/);
    return HttpResponse.json(
      {
        id: `page-${Date.now()}`,
        title: titleMatch?.[1] ?? "Untitled",
        createdDateTime: new Date().toISOString(),
        contentUrl: `${BASE}/me/onenote/pages/page-${Date.now()}/content`,
      },
      { status: 201 }
    );
  }),

  // Get page content (returns HTML, not JSON)
  http.get(`${BASE}/me/onenote/pages/:pageId/content`, () => {
    return new HttpResponse(pageContentFixture, {
      headers: { "Content-Type": "text/html" },
    });
  }),

  // PATCH page content
  http.patch(`${BASE}/me/onenote/pages/:pageId/content`, () => {
    return new HttpResponse(null, { status: 204 });
  }),

  // Simulate 429 rate limit (use special notebook ID to trigger)
  http.get(`${BASE}/me/onenote/notebooks/trigger-429/sections`, () => {
    return new HttpResponse(
      JSON.stringify({ error: { code: "429", message: "Too many requests" } }),
      {
        status: 429,
        headers: { "Retry-After": "5", "Content-Type": "application/json" },
      }
    );
  }),
];
```

### Step 3: MSW Setup for Tests

```typescript
// tests/setup.ts
import { setupServer } from "msw/node";
import { graphHandlers } from "./mocks/graph-handlers";

export const mockServer = setupServer(...graphHandlers);

// Start before all tests, reset between tests, close after
beforeAll(() => mockServer.listen({ onUnhandledRequest: "warn" }));
afterEach(() => mockServer.resetHandlers());
afterAll(() => mockServer.close());
```

```json
// vitest.config.ts addition
{
  "test": {
    "setupFiles": ["./tests/setup.ts"]
  }
}
```

### Step 4: Realistic Fixture Data

Use [Graph Explorer](https://developer.microsoft.com/en-us/graph/graph-explorer) to capture real responses, then save them as fixtures.

```json
// tests/fixtures/notebooks.json
{
  "@odata.context": "https://graph.microsoft.com/v1.0/$metadata#users('user-id')/onenote/notebooks",
  "value": [
    {
      "id": "notebook-abc-123",
      "displayName": "Work Notes",
      "createdDateTime": "2026-01-15T10:00:00Z",
      "lastModifiedDateTime": "2026-03-22T14:30:00Z",
      "isDefault": true,
      "isShared": false,
      "sectionsUrl": "https://graph.microsoft.com/v1.0/me/onenote/notebooks/notebook-abc-123/sections",
      "self": "https://graph.microsoft.com/v1.0/me/onenote/notebooks/notebook-abc-123"
    },
    {
      "id": "notebook-def-456",
      "displayName": "Project Alpha",
      "createdDateTime": "2026-02-01T09:00:00Z",
      "lastModifiedDateTime": "2026-03-20T16:45:00Z",
      "isDefault": false,
      "isShared": true,
      "sectionsUrl": "https://graph.microsoft.com/v1.0/me/onenote/notebooks/notebook-def-456/sections",
      "self": "https://graph.microsoft.com/v1.0/me/onenote/notebooks/notebook-def-456"
    }
  ]
}
```

```html
<!-- tests/fixtures/page-content.html -->
<!-- NOTE: This is OUTPUT format — Graph normalizes your input HTML -->
<!-- Output includes data-id attributes, absolute positioning, div wrappers -->
<html lang="en-US">
  <head>
    <title>Sprint Planning Notes</title>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
  </head>
  <body data-absolute-enabled="true" style="font-family:Calibri;font-size:11pt">
    <div id="div-{guid}" data-id="div1" style="position:absolute;left:48px;top:115px;width:624px">
      <h1 style="font-size:16pt;color:#1e4e79;margin-top:11pt;margin-bottom:11pt">
        Sprint Planning Notes
      </h1>
      <p data-id="p1">Attendees: Alice, Bob, Charlie</p>
      <h2 style="font-size:14pt;color:#2e74b5;margin-top:11pt;margin-bottom:11pt">
        Action Items
      </h2>
      <ul>
        <li da

Related in Backend & APIs