90 lines
3.2 KiB
TypeScript
90 lines
3.2 KiB
TypeScript
|
|
import { provideHttpClient } from '@angular/common/http';
|
||
|
|
import { HttpTestingController, provideHttpClientTesting } from '@angular/common/http/testing';
|
||
|
|
import { TestBed } from '@angular/core/testing';
|
||
|
|
import { ApiConfigService } from '../core/config/api-config.service';
|
||
|
|
import { LocalStorageService } from '../core/storage/local-storage.service';
|
||
|
|
import { LocationService } from './location.service';
|
||
|
|
|
||
|
|
/**
|
||
|
|
* FH-1.1. detectLocation() used to call http://ip-api.com directly. On an
|
||
|
|
* HTTPS storefront the browser blocks mixed active content, so the request
|
||
|
|
* never completed and region auto-detect silently did nothing in production
|
||
|
|
* - and the attempt still exposed the visitor's IP to a third party.
|
||
|
|
*
|
||
|
|
* These tests pin both halves of the fix: geo resolution goes to our own
|
||
|
|
* tenant API, and nothing in this service reaches a foreign origin.
|
||
|
|
*/
|
||
|
|
describe('LocationService', () => {
|
||
|
|
const baseUrl = 'https://api.gorbushka.market';
|
||
|
|
let service: LocationService;
|
||
|
|
let httpTesting: HttpTestingController;
|
||
|
|
|
||
|
|
beforeEach(() => {
|
||
|
|
TestBed.configureTestingModule({
|
||
|
|
providers: [
|
||
|
|
LocationService,
|
||
|
|
provideHttpClient(),
|
||
|
|
provideHttpClientTesting(),
|
||
|
|
{ provide: ApiConfigService, useValue: { getBaseUrl: () => baseUrl } },
|
||
|
|
{
|
||
|
|
provide: LocalStorageService,
|
||
|
|
useValue: { getJSON: () => null, setJSON: () => {}, removeItem: () => {} },
|
||
|
|
},
|
||
|
|
],
|
||
|
|
});
|
||
|
|
|
||
|
|
service = TestBed.inject(LocationService);
|
||
|
|
httpTesting = TestBed.inject(HttpTestingController);
|
||
|
|
// The constructor loads regions; flush it so each test starts clean.
|
||
|
|
httpTesting.expectOne(`${baseUrl}/regions`).flush([]);
|
||
|
|
});
|
||
|
|
|
||
|
|
afterEach(() => httpTesting.verify());
|
||
|
|
|
||
|
|
it('resolves geo through the tenant API, not a third-party host', () => {
|
||
|
|
service.detectLocation();
|
||
|
|
|
||
|
|
const request = httpTesting.expectOne(`${baseUrl}/geo/resolve`);
|
||
|
|
expect(request.request.method).toBe('GET');
|
||
|
|
request.flush({ city: 'Москва', country: 'Россия', countryCode: 'RU' });
|
||
|
|
});
|
||
|
|
|
||
|
|
it('issues no request to a foreign or plaintext origin', () => {
|
||
|
|
service.detectLocation();
|
||
|
|
|
||
|
|
for (const request of httpTesting.match(() => true)) {
|
||
|
|
expect(request.request.url.startsWith(baseUrl))
|
||
|
|
.withContext(`unexpected off-origin request: ${request.request.url}`)
|
||
|
|
.toBe(true);
|
||
|
|
expect(request.request.url.startsWith('http://'))
|
||
|
|
.withContext(`plaintext request: ${request.request.url}`)
|
||
|
|
.toBe(false);
|
||
|
|
request.flush({});
|
||
|
|
}
|
||
|
|
});
|
||
|
|
|
||
|
|
it('degrades to the manual picker when geo resolution fails', () => {
|
||
|
|
service.detectLocation();
|
||
|
|
httpTesting
|
||
|
|
.expectOne(`${baseUrl}/geo/resolve`)
|
||
|
|
.flush(null, { status: 503, statusText: 'Service Unavailable' });
|
||
|
|
|
||
|
|
expect(service.region()).toBeNull();
|
||
|
|
expect(service.detecting()).toBe(false);
|
||
|
|
expect(service.autoDetected()).toBe(true);
|
||
|
|
});
|
||
|
|
|
||
|
|
it('does not re-request geo once detection has been attempted', () => {
|
||
|
|
service.detectLocation();
|
||
|
|
httpTesting.expectOne(`${baseUrl}/geo/resolve`).flush({
|
||
|
|
city: 'Ереван',
|
||
|
|
country: 'Армения',
|
||
|
|
countryCode: 'AM',
|
||
|
|
});
|
||
|
|
|
||
|
|
service.detectLocation();
|
||
|
|
|
||
|
|
httpTesting.expectNone(`${baseUrl}/geo/resolve`);
|
||
|
|
});
|
||
|
|
});
|