Connect OSM account
This commit is contained in:
102
tests/integration/components/user-menu-test.gjs
Normal file
102
tests/integration/components/user-menu-test.gjs
Normal file
@@ -0,0 +1,102 @@
|
||||
import { module, test } from 'qunit';
|
||||
import { setupRenderingTest } from 'marco/tests/helpers';
|
||||
import { render, click } from '@ember/test-helpers';
|
||||
import Service from '@ember/service';
|
||||
import { tracked } from '@glimmer/tracking';
|
||||
import UserMenu from 'marco/components/user-menu';
|
||||
|
||||
class MockOsmAuthService extends Service {
|
||||
@tracked isConnected = false;
|
||||
@tracked userDisplayName = null;
|
||||
|
||||
loginCalled = false;
|
||||
logoutCalled = false;
|
||||
|
||||
login() {
|
||||
this.loginCalled = true;
|
||||
}
|
||||
|
||||
logout() {
|
||||
this.logoutCalled = true;
|
||||
}
|
||||
}
|
||||
|
||||
class MockStorageService extends Service {
|
||||
@tracked connected = false;
|
||||
@tracked userAddress = null;
|
||||
}
|
||||
|
||||
module('Integration | Component | user-menu', function (hooks) {
|
||||
setupRenderingTest(hooks);
|
||||
|
||||
hooks.beforeEach(function () {
|
||||
this.owner.register('service:osmAuth', MockOsmAuthService);
|
||||
this.owner.register('service:storage', MockStorageService);
|
||||
this.osmAuth = this.owner.lookup('service:osmAuth');
|
||||
});
|
||||
|
||||
test('it renders disconnected OSM state correctly', async function (assert) {
|
||||
this.osmAuth.isConnected = false;
|
||||
|
||||
this.storageArg = {
|
||||
connected: false,
|
||||
userAddress: null,
|
||||
};
|
||||
this.onClose = () => {};
|
||||
|
||||
await render(
|
||||
<template>
|
||||
<UserMenu @storage={{this.storageArg}} @onClose={{this.onClose}} />
|
||||
</template>
|
||||
);
|
||||
|
||||
assert
|
||||
.dom('.account-list .account-item:nth-child(2) .account-info')
|
||||
.includesText('OpenStreetMap');
|
||||
assert
|
||||
.dom('.account-list .account-item:nth-child(2) button')
|
||||
.hasText('Connect');
|
||||
assert
|
||||
.dom('.account-list .account-item:nth-child(2) .account-status')
|
||||
.hasText('Not connected');
|
||||
|
||||
await click('.account-list .account-item:nth-child(2) button');
|
||||
assert.true(
|
||||
this.osmAuth.loginCalled,
|
||||
'osmAuth.login() was called when Connect is clicked'
|
||||
);
|
||||
});
|
||||
|
||||
test('it renders connected OSM state correctly', async function (assert) {
|
||||
this.osmAuth.isConnected = true;
|
||||
this.osmAuth.userDisplayName = 'TestMapper';
|
||||
|
||||
this.storageArg = {
|
||||
connected: false,
|
||||
userAddress: null,
|
||||
};
|
||||
this.onClose = () => {};
|
||||
|
||||
await render(
|
||||
<template>
|
||||
<UserMenu @storage={{this.storageArg}} @onClose={{this.onClose}} />
|
||||
</template>
|
||||
);
|
||||
|
||||
assert
|
||||
.dom('.account-list .account-item:nth-child(2) .account-info')
|
||||
.includesText('OpenStreetMap');
|
||||
assert
|
||||
.dom('.account-list .account-item:nth-child(2) button')
|
||||
.hasText('Disconnect');
|
||||
assert
|
||||
.dom('.account-list .account-item:nth-child(2) .account-status')
|
||||
.hasText('TestMapper');
|
||||
|
||||
await click('.account-list .account-item:nth-child(2) button');
|
||||
assert.true(
|
||||
this.osmAuth.logoutCalled,
|
||||
'osmAuth.logout() was called when Disconnect is clicked'
|
||||
);
|
||||
});
|
||||
});
|
||||
47
tests/unit/routes/oauth/osm-callback-test.js
Normal file
47
tests/unit/routes/oauth/osm-callback-test.js
Normal 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'
|
||||
);
|
||||
});
|
||||
});
|
||||
156
tests/unit/services/osm-auth-test.js
Normal file
156
tests/unit/services/osm-auth-test.js
Normal 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'
|
||||
);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user