Connect OSM account
All checks were successful
CI / Lint (pull_request) Successful in 30s
CI / Test (pull_request) Successful in 49s
Release Drafter / Update release notes draft (pull_request) Successful in 4s

This commit is contained in:
2026-04-01 18:35:01 +04:00
parent 2dfd411837
commit e7dfed204e
12 changed files with 482 additions and 8 deletions

View File

@@ -0,0 +1,47 @@
import { module, test } from 'qunit';
import { setupTest } from 'marco/tests/helpers';
import Service from '@ember/service';
class MockOsmAuthService extends Service {
handleCallbackCalled = false;
async handleCallback() {
this.handleCallbackCalled = true;
return Promise.resolve();
}
}
class MockRouterService extends Service {
transitionToArgs = [];
transitionTo(...args) {
this.transitionToArgs.push(args);
}
}
module('Unit | Route | oauth/osm-callback', function (hooks) {
setupTest(hooks);
hooks.beforeEach(function () {
this.owner.register('service:osmAuth', MockOsmAuthService);
this.owner.register('service:router', MockRouterService);
});
test('it handles the callback and transitions to index', async function (assert) {
let route = this.owner.lookup('route:oauth/osm-callback');
let osmAuth = this.owner.lookup('service:osmAuth');
let router = this.owner.lookup('service:router');
await route.model();
assert.true(
osmAuth.handleCallbackCalled,
'handleCallback was called on the osmAuth service'
);
assert.deepEqual(
router.transitionToArgs,
[['index']],
'router transitioned back to index route'
);
});
});

View File

@@ -0,0 +1,156 @@
import { module, test } from 'qunit';
import { setupTest } from 'marco/tests/helpers';
module('Unit | Service | osm-auth', function (hooks) {
setupTest(hooks);
let originalLocalStorage;
let originalFetch;
hooks.beforeEach(function () {
// Stub localStorage
let mockStorage = {};
originalLocalStorage = window.localStorage;
Object.defineProperty(window, 'localStorage', {
value: {
getItem: (key) => mockStorage[key] || null,
setItem: (key, value) => {
mockStorage[key] = value.toString();
},
removeItem: (key) => {
delete mockStorage[key];
},
clear: () => {
mockStorage = {};
},
},
writable: true,
});
// Stub fetch
originalFetch = window.fetch;
window.fetch = async () => {
return {
ok: true,
json: async () => ({
user: { display_name: 'MockedUser' },
}),
};
};
});
hooks.afterEach(function () {
window.localStorage = originalLocalStorage;
window.fetch = originalFetch;
});
test('it restores session correctly when logged in', async function (assert) {
let service = this.owner.factoryFor('service:osm-auth').create();
// Stub the underlying oauthClient before it runs restoreSession (actually restoreSession runs in constructor, so we need to intercept it)
// Because restoreSession runs in the constructor, we might need to overwrite it after, but it's async.
// Let's just create it, let the original restoreSession fail or do nothing, and then we stub and re-call it.
service.oauthClient.isAuthorized = async () => true;
window.localStorage.setItem('marco:osm_user_display_name', 'CachedName');
await service.restoreSession();
assert.true(service.isConnected, 'Service becomes connected');
assert.strictEqual(
service.userDisplayName,
'CachedName',
'Restores name from localStorage'
);
});
test('it fetches user info when logged in but no cached name', async function (assert) {
let service = this.owner.factoryFor('service:osm-auth').create();
service.oauthClient.isAuthorized = async () => true;
service.oauthClient.getTokens = async () => ({ accessToken: 'fake-token' });
// Ensure localStorage is empty for this key
window.localStorage.removeItem('marco:osm_user_display_name');
await service.restoreSession();
assert.true(service.isConnected, 'Service becomes connected');
assert.strictEqual(
service.userDisplayName,
'MockedUser',
'Fetched name from API'
);
assert.strictEqual(
window.localStorage.getItem('marco:osm_user_display_name'),
'MockedUser',
'Saved name to localStorage'
);
});
test('it handles login()', async function (assert) {
let service = this.owner.lookup('service:osm-auth');
let requestCalled = false;
service.oauthClient.requestAuthorizationCode = async () => {
requestCalled = true;
};
await service.login();
assert.true(
requestCalled,
'Delegates to oauthClient.requestAuthorizationCode()'
);
});
test('it handles logout()', async function (assert) {
let service = this.owner.lookup('service:osm-auth');
service.isConnected = true;
service.userDisplayName = 'ToBeCleared';
window.localStorage.setItem('marco:osm_user_display_name', 'ToBeCleared');
let resetCalled = false;
service.oauthClient.reset = async () => {
resetCalled = true;
};
await service.logout();
assert.true(resetCalled, 'Delegates to oauthClient.reset()');
assert.false(service.isConnected, 'isConnected is reset');
assert.strictEqual(
service.userDisplayName,
null,
'userDisplayName is reset'
);
assert.strictEqual(
window.localStorage.getItem('marco:osm_user_display_name'),
null,
'localStorage is cleared'
);
});
test('it handles the callback flow', async function (assert) {
let service = this.owner.lookup('service:osm-auth');
let receiveCodeCalled = false;
let getTokensCalled = false;
service.oauthClient.receiveCode = async () => {
receiveCodeCalled = true;
};
service.oauthClient.getTokens = async () => {
getTokensCalled = true;
return { accessToken: 'fake-token' };
};
await service.handleCallback();
assert.true(receiveCodeCalled, 'oauthClient.receiveCode was called');
assert.true(getTokensCalled, 'oauthClient.getTokens was called');
assert.true(service.isConnected, 'Service is connected after callback');
assert.strictEqual(
service.userDisplayName,
'MockedUser',
'User info fetched and set during callback'
);
});
});