-
-
Notifications
You must be signed in to change notification settings - Fork 206
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
e87b350
commit 0c76ed4
Showing
1 changed file
with
146 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,7 +1,7 @@ | ||
import { Messenger } from '@metamask/base-controller'; | ||
import { strict as assert } from 'assert'; | ||
import nock from 'nock'; | ||
import * as sinon from 'sinon'; | ||
import sinon from 'sinon'; | ||
|
||
import { | ||
ListNames, | ||
|
@@ -13,6 +13,8 @@ import { | |
type PhishingControllerOptions, | ||
CLIENT_SIDE_DETECION_BASE_URL, | ||
C2_DOMAIN_BLOCKLIST_ENDPOINT, | ||
PHISHING_DETECTION_BASE_URL, | ||
PHISHING_DETECTION_SCAN_ENDPOINT, | ||
} from './PhishingController'; | ||
import { formatHostnameToUrl } from './tests/utils'; | ||
import { PhishingDetectorResultType } from './types'; | ||
|
@@ -2365,4 +2367,147 @@ describe('PhishingController', () => { | |
expect(controller.state.whitelist).toHaveLength(1); | ||
}); | ||
}); | ||
|
||
describe('scanUrl', () => { | ||
let controller: PhishingController; | ||
let clock: sinon.SinonFakeTimers; | ||
const testUrl: string = 'https://example.com'; | ||
const mockResponse = { | ||
domainName: 'example.com', | ||
recommendedAction: 'NONE', | ||
riskFactors: [], | ||
verified: true, | ||
status: 'COMPLETE', | ||
}; | ||
|
||
beforeEach(() => { | ||
controller = getPhishingController(); | ||
clock = sinon.useFakeTimers(); | ||
}); | ||
|
||
it('should return the scan result', async () => { | ||
const scope = nock(PHISHING_DETECTION_BASE_URL) | ||
.get(`/${PHISHING_DETECTION_SCAN_ENDPOINT}`) | ||
.query({ url: 'example.com' }) | ||
.reply(200, mockResponse); | ||
|
||
const response = await controller.scanUrl(testUrl); | ||
expect(response).toMatchObject(mockResponse); | ||
expect(scope.isDone()).toBe(true); | ||
}); | ||
|
||
it('should throw error on failed request', async () => { | ||
const scope = nock(PHISHING_DETECTION_BASE_URL) | ||
.get(`/${PHISHING_DETECTION_SCAN_ENDPOINT}`) | ||
.query({ url: 'example.com' }) | ||
.reply(500); | ||
|
||
await expect(controller.scanUrl(testUrl)).rejects.toThrow( | ||
'request failed: 500 Internal Server Error', | ||
); | ||
expect(scope.isDone()).toBe(true); | ||
}); | ||
|
||
it('should timeout after 8000ms', async () => { | ||
const scope = nock(PHISHING_DETECTION_BASE_URL) | ||
.get(`/${PHISHING_DETECTION_SCAN_ENDPOINT}`) | ||
.query({ url: testUrl }) | ||
.delayConnection(10000) | ||
.reply(200, {}); | ||
|
||
const promise = controller.scanUrl(testUrl); | ||
clock.tick(8000); | ||
await expect(promise).rejects.toThrow( | ||
'request failed: Request timed out', | ||
); | ||
expect(scope.isDone()).toBe(false); | ||
}); | ||
|
||
it('should only send hostname when URL contains query parameters', async () => { | ||
const urlWithQuery = | ||
'https://example.com/path?param1=value1¶m2=value2'; | ||
const expectedHostname = 'example.com'; | ||
|
||
const scope = nock(PHISHING_DETECTION_BASE_URL) | ||
.get(`/${PHISHING_DETECTION_SCAN_ENDPOINT}`) | ||
.query({ url: expectedHostname }) | ||
.reply(200, mockResponse); | ||
|
||
const response = await controller.scanUrl(urlWithQuery); | ||
expect(response).toMatchObject(mockResponse); | ||
expect(scope.isDone()).toBe(true); | ||
}); | ||
|
||
it('should only send hostname when URL contains hash fragments', async () => { | ||
const urlWithHash = 'https://example.com/page#section1'; | ||
const expectedHostname = 'example.com'; | ||
|
||
const scope = nock(PHISHING_DETECTION_BASE_URL) | ||
.get(`/${PHISHING_DETECTION_SCAN_ENDPOINT}`) | ||
.query({ url: expectedHostname }) | ||
.reply(200, mockResponse); | ||
|
||
const response = await controller.scanUrl(urlWithHash); | ||
expect(response).toMatchObject(mockResponse); | ||
expect(scope.isDone()).toBe(true); | ||
}); | ||
|
||
it('should only send hostname for complex URLs with multiple parameters', async () => { | ||
const complexUrl = | ||
'https://sub.example.com:8080/path/to/page?q=search&utm_source=test#top'; | ||
const expectedHostname = 'sub.example.com'; | ||
|
||
const subdomainResponse = { | ||
...mockResponse, | ||
domainName: 'sub.example.com', | ||
}; | ||
|
||
const scope = nock(PHISHING_DETECTION_BASE_URL) | ||
.get(`/${PHISHING_DETECTION_SCAN_ENDPOINT}`) | ||
.query({ url: expectedHostname }) | ||
.reply(200, subdomainResponse); | ||
|
||
const response = await controller.scanUrl(complexUrl); | ||
expect(response).toMatchObject(subdomainResponse); | ||
expect(scope.isDone()).toBe(true); | ||
}); | ||
|
||
it('should throw error for invalid URLs', async () => { | ||
const invalidUrls = [ | ||
'not-a-url', | ||
'http://', | ||
'https://', | ||
'example', | ||
'http://.', | ||
'http://..', | ||
'http://../', | ||
'http://?', | ||
'http://??', | ||
'http://??/', | ||
'http://#', | ||
'http://##', | ||
'http://##/', | ||
]; | ||
|
||
for (const invalidUrl of invalidUrls) { | ||
await expect(controller.scanUrl(invalidUrl)).rejects.toThrow( | ||
'Invalid URL provided', | ||
); | ||
} | ||
}); | ||
|
||
it('should handle URLs with authentication parameters correctly', async () => { | ||
const urlWithAuth = 'https://user:[email protected]/secure'; | ||
const expectedHostname = 'example.com'; | ||
|
||
const scope = nock(PHISHING_DETECTION_BASE_URL) | ||
.get(`/${PHISHING_DETECTION_SCAN_ENDPOINT}`) | ||
.query({ url: expectedHostname }) | ||
.reply(200, mockResponse); | ||
|
||
const response = await controller.scanUrl(urlWithAuth); | ||
expect(response).toMatchObject(mockResponse); | ||
expect(scope.isDone()).toBe(true); | ||
}); | ||
}); | ||
}); |