microsoft-onedrive-mock 1.0.0 → 1.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Daniel Meyer
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1 +1,76 @@
1
- # microsoft-onedrive-mock
1
+ # microsoft-onedrive-mock
2
+
3
+ <br />
4
+
5
+ <p style="text-align: center;">
6
+ Mock-Server that simulates being Microsoft OneDrive (Graph API).<br />
7
+ Used for testing the <a href="https://rxdb.info/" target="_blank">RxDB OneDrive-Sync</a>.<br />
8
+ Sister project to <a href="https://github.com/pubkey/google-drive-mock" target="_blank">google-drive-mock</a>.<br />
9
+ Mostly Vibe-Coded.<br />
10
+ </p>
11
+
12
+ ## Installation
13
+
14
+ ```bash
15
+ npm install microsoft-onedrive-mock
16
+ ```
17
+
18
+ ## Usage
19
+
20
+ ```typescript
21
+ import { startServer } from 'microsoft-onedrive-mock';
22
+
23
+ // start the server
24
+ const port = 3000;
25
+ const server = startServer(port);
26
+
27
+ // Read the drive root
28
+ const readResponse = await fetch(`http://localhost:3000/v1.0/me/drive/root`, {
29
+ method: 'GET',
30
+ headers: {
31
+ 'Authorization': 'Bearer valid-token'
32
+ }
33
+ });
34
+ const folderContent = await readResponse.json();
35
+ console.log('Read Root:', folderContent);
36
+
37
+ // Stop the server
38
+ server.close();
39
+
40
+ ```
41
+
42
+ ## Tech
43
+
44
+ - TypeScript
45
+ - Express
46
+ - Vitest
47
+
48
+ ## Browser Testing
49
+
50
+ To run tests inside a headless browser (Chromium):
51
+
52
+ ```bash
53
+ npm run test:browser
54
+ ```
55
+
56
+ ## Real Microsoft Graph API Testing
57
+
58
+ To run tests against the real Microsoft Graph / OneDrive API instead of the mock:
59
+
60
+ 1. Create a `.ENV` file (see `.ENV_EXAMPLE`):
61
+ ```
62
+ TEST_TARGET=real
63
+ ONEDRIVE_TOKEN=your-access-token
64
+ ```
65
+ 2. You can generate a valid `ONEDRIVE_TOKEN` quickly by running the included login script and following the browser prompts:
66
+ ```bash
67
+ npm run example:login
68
+ ```
69
+ 3. Run tests:
70
+ ```bash
71
+ npm run test:real
72
+ ```
73
+
74
+ ## Contributing
75
+
76
+ GitHub issues for this project are closed. If you find a bug, please create a Pull Request with a test case reproducing the issue.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "microsoft-onedrive-mock",
3
- "version": "1.0.0",
3
+ "version": "1.0.1",
4
4
  "description": "Mock-Server that simulates being Microsoft OneDrive. Used for testing.",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -18,13 +18,33 @@
18
18
  "lint": "eslint .",
19
19
  "lint:fix": "eslint . --fix"
20
20
  },
21
+ "repository": {
22
+ "type": "git",
23
+ "url": "git+https://github.com/pubkey/microsoft-onedrive-mock.git"
24
+ },
25
+ "keywords": [
26
+ "microsoft-onedrive",
27
+ "onedrive",
28
+ "mock",
29
+ "testing",
30
+ "server",
31
+ "api",
32
+ "simulator",
33
+ "rxdb"
34
+ ],
35
+ "author": "pubkey",
36
+ "license": "MIT",
37
+ "bugs": {
38
+ "url": "https://github.com/pubkey/microsoft-onedrive-mock/issues"
39
+ },
40
+ "homepage": "https://github.com/pubkey/microsoft-onedrive-mock#readme",
21
41
  "type": "commonjs",
22
42
  "peerDependencies": {
23
43
  "express": "5.2.1"
24
44
  },
25
45
  "devDependencies": {
26
46
  "@eslint/js": "9.39.2",
27
- "@microsoft/microsoft-graph-types": "^2.43.1",
47
+ "@microsoft/microsoft-graph-types": "2.43.1",
28
48
  "@types/cors": "2.8.19",
29
49
  "@types/express": "5.0.6",
30
50
  "@types/node": "25.1.0",
@@ -40,9 +60,9 @@
40
60
  "playwright": "1.58.0",
41
61
  "start-server-and-test": "2.1.3",
42
62
  "supertest": "7.2.2",
43
- "ts-node": "^10.9.2",
63
+ "ts-node": "10.9.2",
44
64
  "typescript": "5.9.3",
45
65
  "typescript-eslint": "8.54.0",
46
66
  "vitest": "4.0.18"
47
67
  }
48
- }
68
+ }
package/.ENV_EXAMPLE DELETED
@@ -1,16 +0,0 @@
1
- # To run tests against the real Microsoft Graph API
2
- # Copy this file to .ENV and fill in the values
3
-
4
- # 'mock' (default) or 'real'
5
- TEST_TARGET=real
6
-
7
- # OAuth2 Access Token for your Microsoft Account
8
- # Get one via 'npm run example:login'
9
- ONEDRIVE_TOKEN=...
10
-
11
- # Optional: Simulate latency in ms (only works with TEST_TARGET=mock)
12
- # LATENCY=50
13
-
14
- # Client ID for the Microsoft Login Example
15
- ONEDRIVE_CLIENT_ID=e5...
16
- ONEDRIVE_TENANT_ID=common
@@ -1,41 +0,0 @@
1
- name: CI
2
-
3
- on:
4
- push:
5
- branches: [ main, master ]
6
- pull_request:
7
- branches: [ main, master ]
8
-
9
- jobs:
10
- build-and-test:
11
- runs-on: ubuntu-24.04
12
-
13
- steps:
14
- - uses: actions/checkout@v4
15
-
16
- - name: Use Node.js
17
- uses: actions/setup-node@v4
18
- with:
19
- node-version: '24'
20
- cache: 'npm'
21
-
22
- - name: Install dependencies
23
- run: npm install
24
-
25
- - name: Lint
26
- run: npm run lint
27
-
28
- - name: Build
29
- run: npm run build
30
-
31
- - name: Test (Node)
32
- run: npm test
33
-
34
- - name: Install Playwright Browsers
35
- run: npx playwright@1.58.0 install chromium
36
-
37
- - name: Test (Browser)
38
- run: npm run test:browser
39
-
40
- - name: Test Slow
41
- run: npm run test:slow
@@ -1,50 +0,0 @@
1
- name: Release
2
-
3
- on:
4
- workflow_dispatch:
5
- inputs:
6
- version:
7
- description: 'New Version (e.g. 1.0.0, or leave empty for patch)'
8
- required: false
9
- type: string
10
-
11
- jobs:
12
- build:
13
- runs-on: ubuntu-24.04
14
- # @link https://docs.github.com/en/actions/using-jobs/assigning-permissions-to-jobs
15
- permissions:
16
- contents: write
17
- # @link https://docs.npmjs.com/generating-provenance-statements#about-npm-provenance
18
- id-token: write
19
- steps:
20
- - uses: actions/checkout@v4
21
- - uses: actions/setup-node@v4
22
- with:
23
- node-version: '24'
24
- registry-url: 'https://registry.npmjs.org'
25
- cache: 'npm'
26
-
27
- - run: npm install
28
- - name: Bump Version
29
- id: bump
30
- run: |
31
- if [ -z "${{ inputs.version }}" ]; then
32
- VERSION=$(npm version patch --no-git-tag-version)
33
- else
34
- VERSION=$(npm version ${{ inputs.version }} --no-git-tag-version)
35
- fi
36
- echo "version=${VERSION}" >> $GITHUB_OUTPUT
37
- - run: npm run build
38
- - run: npm run lint
39
- - run: npm test
40
- - run: npm publish
41
- env:
42
- NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
43
-
44
- - name: Push changes
45
- run: |
46
- git config --global user.name 'github-actions[bot]'
47
- git config --global user.email 'github-actions[bot]@users.noreply.github.com'
48
- git add package.json package-lock.json
49
- git commit -m "release: ${{ steps.bump.outputs.version }}"
50
- git push
package/eslint.config.mjs DELETED
@@ -1,15 +0,0 @@
1
- import globals from "globals";
2
- import pluginJs from "@eslint/js";
3
- import tseslint from "typescript-eslint";
4
-
5
-
6
- /** @type {import('eslint').Linter.Config[]} */
7
- export default [
8
- { files: ["**/*.{js,mjs,cjs,ts}"] },
9
- { languageOptions: { globals: globals.node } },
10
- pluginJs.configs.recommended,
11
- ...tseslint.configs.recommended,
12
- {
13
- ignores: ["dist/**", "coverage/**", "node_modules/**"]
14
- }
15
- ];
@@ -1,84 +0,0 @@
1
- import * as fs from 'fs';
2
- import * as path from 'path';
3
-
4
- const clientId = 'e5f346a8-8996-4d46-9f93-6e6817d9078e';
5
- const tenantId = 'common';
6
-
7
- async function runDeviceLogin() {
8
- const authority = `https://login.microsoftonline.com/${tenantId}/oauth2/v2.0`;
9
-
10
- // 1. Get device code
11
- const deviceCodeParams = new URLSearchParams();
12
- deviceCodeParams.append('client_id', clientId);
13
- deviceCodeParams.append('scope', 'Files.ReadWrite.All User.Read offline_access');
14
-
15
- let deviceCodeRes;
16
- try {
17
- deviceCodeRes = await fetch(`${authority}/devicecode`, {
18
- method: 'POST',
19
- headers: {
20
- 'Content-Type': 'application/x-www-form-urlencoded'
21
- },
22
- body: deviceCodeParams.toString()
23
- });
24
- } catch (e) {
25
- console.error("Network error executing fetch request:", e);
26
- return;
27
- }
28
-
29
- const deviceCodeData = await deviceCodeRes.json();
30
- if (!deviceCodeRes.ok) {
31
- console.error('❌ Failed to start device login:', deviceCodeData);
32
- console.error('\nNOTE: Your Azure App must have "Allow public client flows" enabled in Authentication settings for Device Code flow to work.');
33
- return;
34
- }
35
-
36
- console.log('\n====================================================');
37
- console.log(deviceCodeData.message);
38
- console.log('====================================================\n');
39
-
40
- // 2. Poll for token
41
- const tokenParams = new URLSearchParams();
42
- tokenParams.append('grant_type', 'urn:ietf:params:oauth:grant-type:device_code');
43
- tokenParams.append('client_id', clientId);
44
- tokenParams.append('device_code', deviceCodeData.device_code);
45
-
46
- let polling = true;
47
- while (polling) {
48
- await new Promise(resolve => setTimeout(resolve, deviceCodeData.interval * 1000));
49
-
50
- const tokenRes = await fetch(`${authority}/token`, {
51
- method: 'POST',
52
- headers: {
53
- 'Content-Type': 'application/x-www-form-urlencoded'
54
- },
55
- body: tokenParams.toString()
56
- });
57
-
58
- const tokenData = await tokenRes.json();
59
-
60
- if (tokenRes.ok) {
61
- console.log('\n✅ Successfully authenticated!');
62
-
63
- // Save to .ENV
64
- const envPath = path.resolve(__dirname, '../.ENV');
65
- let envContent = fs.existsSync(envPath) ? fs.readFileSync(envPath, 'utf8') : '';
66
- if (envContent.includes('ONEDRIVE_TOKEN=')) {
67
- envContent = envContent.replace(/ONEDRIVE_TOKEN=[^\n]*/, `ONEDRIVE_TOKEN=${tokenData.access_token}`);
68
- } else {
69
- envContent += `\nONEDRIVE_TOKEN=${tokenData.access_token}\n`;
70
- }
71
- fs.writeFileSync(envPath, envContent);
72
- console.log('✅ Updated .ENV with your new ONEDRIVE_TOKEN');
73
- console.log('You can now run: npm run test:real');
74
- polling = false;
75
- } else if (tokenData.error !== 'authorization_pending') {
76
- console.error('\n❌ Login failed:', tokenData.error_description || tokenData.error);
77
- polling = false;
78
- } else {
79
- process.stdout.write('.');
80
- }
81
- }
82
- }
83
-
84
- runDeviceLogin();
@@ -1,227 +0,0 @@
1
- <!DOCTYPE html>
2
- <html lang="en">
3
-
4
- <head>
5
- <meta charset="UTF-8">
6
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
7
- <title>Microsoft OneDrive Token Generator</title>
8
- <style>
9
- body {
10
- font-family: sans-serif;
11
- padding: 20px;
12
- max-width: 600px;
13
- margin: 0 auto;
14
- }
15
-
16
- .form-group {
17
- margin-bottom: 15px;
18
- }
19
-
20
- label {
21
- display: block;
22
- margin-bottom: 5px;
23
- font-weight: bold;
24
- }
25
-
26
- input[type="text"] {
27
- width: 100%;
28
- padding: 8px;
29
- box-sizing: border-box;
30
- }
31
-
32
- button {
33
- padding: 10px 20px;
34
- background-color: #0078d4;
35
- color: white;
36
- border: none;
37
- cursor: pointer;
38
- font-size: 16px;
39
- border-radius: 4px;
40
- }
41
-
42
- button:hover {
43
- background-color: #005a9e;
44
- }
45
-
46
- button#copy-btn {
47
- background-color: #28a745;
48
- margin-top: 10px;
49
- }
50
-
51
- button#copy-btn:hover {
52
- background-color: #218838;
53
- }
54
-
55
- #token-display {
56
- margin-top: 20px;
57
- background: #f0f0f0;
58
- padding: 15px;
59
- border-radius: 4px;
60
- display: none;
61
- }
62
-
63
- textarea {
64
- width: 100%;
65
- padding: 8px;
66
- box-sizing: border-box;
67
- border: 1px solid #ccc;
68
- border-radius: 4px;
69
- resize: vertical;
70
- font-family: monospace;
71
- }
72
-
73
- .hidden {
74
- display: none;
75
- }
76
- </style>
77
- <!-- MSAL.js v3 -->
78
- <script type="text/javascript" src="https://alcdn.msauth.net/browser/2.37.1/js/msal-browser.min.js"></script>
79
- </head>
80
-
81
- <body>
82
-
83
- <h1>Microsoft OneDrive Login</h1>
84
- <p>Enter your Client ID (Application ID) and Tenant ID to authenticate and get an access token. The login uses
85
- MSAL.js for OAuth2 Implicit/SPA flow.</p>
86
-
87
- <div class="form-group">
88
- <label for="client-id">Client ID:</label>
89
- <input type="text" id="client-id" placeholder="e.g. 11111111-1111-1111-1111-111111111111">
90
- </div>
91
-
92
- <div class="form-group">
93
- <label for="tenant-id">Tenant ID:</label>
94
- <input type="text" id="tenant-id" placeholder="e.g. common (or your specific tenant GUID)">
95
- </div>
96
-
97
- <button id="login-btn" onclick="handleAuth()">Login to Microsoft Graph</button>
98
-
99
- <div id="token-display">
100
- <h3>Access Token:</h3>
101
- <textarea id="access-token" readonly rows="6"></textarea>
102
- <button id="copy-btn" onclick="copyToken()">Copy Token</button>
103
-
104
- <h3>Full .ENV Content:</h3>
105
- <textarea id="env-content" readonly rows="12"></textarea>
106
- <button id="copy-env-btn" onclick="copyEnv()">Copy .ENV</button>
107
- </div>
108
-
109
- <script>
110
- let msalInstance = null;
111
- let loginRequest = {
112
- scopes: ["Files.ReadWrite.All", "User.Read"]
113
- };
114
-
115
- window.onload = async function () {
116
- const storedClientId = localStorage.getItem('ms_client_id');
117
- const storedTenantId = localStorage.getItem('ms_tenant_id') || 'common';
118
- if (storedClientId) document.getElementById('client-id').value = storedClientId;
119
- document.getElementById('tenant-id').value = storedTenantId;
120
-
121
- // If we have clientId, we can initialize MSAL to check if we are returning from a redirect
122
- if (storedClientId) {
123
- initializeMsal(storedClientId, storedTenantId);
124
- try {
125
- const response = await msalInstance.handleRedirectPromise();
126
- if (response && response.accessToken) {
127
- displayToken(response.accessToken, storedClientId, storedTenantId);
128
- }
129
- } catch (err) {
130
- console.error("Redirect error:", err);
131
- alert("Failed to process login redirect: " + err.message);
132
- }
133
- }
134
- }
135
-
136
- function initializeMsal(clientId, tenantId) {
137
- // Determine if the user is running on https or http
138
- const currentOrigin = window.location.origin;
139
- const redirectUri = currentOrigin + "/microsoft-login.html";
140
-
141
- const msalConfig = {
142
- auth: {
143
- clientId: clientId,
144
- authority: `https://login.microsoftonline.com/${tenantId}`,
145
- redirectUri: redirectUri,
146
- }
147
- };
148
- msalInstance = new msal.PublicClientApplication(msalConfig);
149
- }
150
-
151
- async function handleAuth() {
152
- const clientId = document.getElementById('client-id').value.trim();
153
- const tenantId = document.getElementById('tenant-id').value.trim() || 'common';
154
-
155
- if (!clientId) {
156
- alert('Please enter a Client ID');
157
- return;
158
- }
159
-
160
- localStorage.setItem('ms_client_id', clientId);
161
- localStorage.setItem('ms_tenant_id', tenantId);
162
-
163
- initializeMsal(clientId, tenantId);
164
-
165
- try {
166
- // Use redirect flow instead of popup. This avoids popup blockers and
167
- // cleanly returns the user to this page with the '#code=...' in the URL,
168
- // which handleRedirectPromise() will then automatically exchange for the JWT Access Token!
169
- await msalInstance.loginRedirect(loginRequest);
170
- } catch (err) {
171
- console.error(err);
172
- alert('Login failed: ' + (err.message || err));
173
- }
174
- }
175
-
176
- function displayToken(accessToken, clientId, tenantId) {
177
- document.getElementById('token-display').style.display = 'block';
178
- document.getElementById('access-token').value = accessToken;
179
-
180
- const envContent = `# To run tests against the real Microsoft Graph API
181
- # Copy this file to .ENV and fill in the values
182
-
183
- # 'mock' (default) or 'real'
184
- TEST_TARGET=real
185
-
186
- # OAuth2 Access Token for your Microsoft Account
187
- # Get one via 'npm run example:login'
188
- ONEDRIVE_TOKEN=${accessToken}
189
-
190
- # Client ID for the Microsoft Login Example
191
- ONEDRIVE_CLIENT_ID=${clientId}
192
- ONEDRIVE_TENANT_ID=${tenantId}
193
-
194
- # Optional: Simulate latency in ms (only works with TEST_TARGET=mock)
195
- # LATENCY=50
196
- `;
197
- document.getElementById('env-content').value = envContent;
198
- }
199
-
200
- function copyToken() {
201
- copyToClipboard('access-token', 'copy-btn');
202
- }
203
-
204
- function copyEnv() {
205
- copyToClipboard('env-content', 'copy-env-btn');
206
- }
207
-
208
- function copyToClipboard(elementId, buttonId) {
209
- const textEl = document.getElementById(elementId);
210
- textEl.select();
211
- textEl.setSelectionRange(0, 99999);
212
-
213
- navigator.clipboard.writeText(textEl.value).then(() => {
214
- const btn = document.getElementById(buttonId);
215
- const originalText = btn.innerText;
216
- btn.innerText = 'Copied!';
217
- setTimeout(() => {
218
- btn.innerText = originalText;
219
- }, 2000);
220
- }).catch(err => {
221
- console.error('Failed to copy text: ', err);
222
- });
223
- }
224
- </script>
225
- </body>
226
-
227
- </html>
@@ -1,11 +0,0 @@
1
- import express from 'express';
2
-
3
- const app = express();
4
- const port = 8080;
5
-
6
- app.use(express.static(__dirname));
7
-
8
- app.listen(port, () => {
9
- console.log(`Login example running at http://localhost:${port}/microsoft-login.html`);
10
- console.log('NOTE: Ensure "http://localhost:8080/microsoft-login.html" is added as a Redirect URI in your Microsoft Entra ID app registration (SPA).');
11
- });
@@ -1,70 +0,0 @@
1
- import * as fs from 'fs';
2
- import * as path from 'path';
3
- import * as https from 'https';
4
-
5
- const envPath = path.resolve(__dirname, '../.ENV');
6
- if (fs.existsSync(envPath)) {
7
- const envContent = fs.readFileSync(envPath, 'utf8');
8
- envContent.split('\n').forEach(line => {
9
- const match = line.match(/^([^=]+)=(.*)$/);
10
- if (match) {
11
- const key = match[1].trim();
12
- const value = match[2].trim().replace(/^['"]|['"]$/g, '');
13
- if (!process.env[key]) {
14
- process.env[key] = value;
15
- }
16
- }
17
- });
18
- }
19
-
20
- const token = process.env.ONEDRIVE_TOKEN || process.env.GRAPH_TOKEN;
21
-
22
- if (!token) {
23
- console.error('❌ Error: ONEDRIVE_TOKEN or GRAPH_TOKEN not found in environment or .ENV file.');
24
- process.exit(1);
25
- }
26
-
27
- console.log('🔄 Verifying Microsoft Graph Token...');
28
-
29
- const options = {
30
- hostname: 'graph.microsoft.com',
31
- path: '/v1.0/me',
32
- method: 'GET',
33
- headers: {
34
- 'Authorization': `Bearer ${token}`,
35
- 'User-Agent': 'node-script'
36
- }
37
- };
38
-
39
- const req = https.request(options, (res) => {
40
- let data = '';
41
-
42
- res.on('data', (chunk) => {
43
- data += chunk;
44
- });
45
-
46
- res.on('end', () => {
47
- if (res.statusCode === 200) {
48
- try {
49
- const body = JSON.parse(data);
50
- console.log(`✅ Token is valid. User: ${body.userPrincipalName || body.displayName || 'Unknown'}`);
51
- process.exit(0);
52
- } catch (e: unknown) {
53
- const msg = e instanceof Error ? e.message : String(e);
54
- console.error('❌ Error parsing response:', msg);
55
- process.exit(1);
56
- }
57
- } else {
58
- console.error(`❌ Token verification failed. Update .ENV file with a valid token. Status: ${res.statusCode}`);
59
- console.error('Response:', data);
60
- process.exit(1);
61
- }
62
- });
63
- });
64
-
65
- req.on('error', (e) => {
66
- console.error(`❌ Request error: ${e.message}`);
67
- process.exit(1);
68
- });
69
-
70
- req.end();