generated from MetaMask/metamask-module-template
-
-
Notifications
You must be signed in to change notification settings - Fork 3
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add establishMetaMaskRepository + OutputLogger
In order to analyze a project, that project must be cloned first along with the MetaMask module template. So, there must be some function that checks for whether a repo has been cloned yet and clones it if not. This commit introduces code to implement the steps involved in doing this. It also introduces a utility class for sending messages to standard out and standard error streams, as well as a version that can be used in tests (which fakes out the streams).
- Loading branch information
Showing
15 changed files
with
2,003 additions
and
11 deletions.
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
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 |
---|---|---|
@@ -0,0 +1,5 @@ | ||
/** | ||
* The number of milliseconds in an hour, used to determine when to pull the | ||
* latest changes for previously cached repositories. | ||
*/ | ||
export const ONE_HOUR = 60 * 60 * 1000; |
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 |
---|---|---|
@@ -0,0 +1,325 @@ | ||
import execa from 'execa'; | ||
import path from 'path'; | ||
|
||
import { establishMetaMaskRepository } from './establish-metamask-repository'; | ||
import { FakeOutputLogger } from '../tests/fake-output-logger'; | ||
import type { PrimaryExecaFunction } from '../tests/helpers'; | ||
import { fakeDateOnly, withinSandbox } from '../tests/helpers'; | ||
import { setupToolWithMockRepository } from '../tests/setup-tool-with-mock-repository'; | ||
|
||
jest.mock('execa'); | ||
|
||
const execaMock = jest.mocked<PrimaryExecaFunction>(execa); | ||
|
||
describe('establishMetaMaskRepository', () => { | ||
beforeEach(() => { | ||
fakeDateOnly(); | ||
}); | ||
|
||
afterEach(() => { | ||
jest.useRealTimers(); | ||
}); | ||
|
||
describe('given the path to an existing directory that is not a Git repository', () => { | ||
it('throws', async () => { | ||
await withinSandbox(async ({ directoryPath: sandboxDirectoryPath }) => { | ||
const outputLogger = new FakeOutputLogger(); | ||
|
||
await expect( | ||
establishMetaMaskRepository({ | ||
repositoryReference: sandboxDirectoryPath, | ||
workingDirectoryPath: sandboxDirectoryPath, | ||
cachedRepositoriesDirectoryPath: sandboxDirectoryPath, | ||
outputLogger, | ||
}), | ||
).rejects.toThrow( | ||
`"${sandboxDirectoryPath}" is not a Git repository, cannot proceed.`, | ||
); | ||
}); | ||
}); | ||
}); | ||
|
||
describe('given the path to an existing repository relative to the working directory', () => { | ||
it('does not pull the latest changes', async () => { | ||
await withinSandbox(async ({ directoryPath: sandboxDirectoryPath }) => { | ||
const workingDirectoryPath = path.join(sandboxDirectoryPath, 'working'); | ||
const { cachedRepositoriesDirectoryPath, repository } = | ||
await setupToolWithMockRepository({ | ||
execaMock, | ||
sandboxDirectoryPath, | ||
repository: { | ||
create: true, | ||
parentDirectoryPath: workingDirectoryPath, | ||
}, | ||
}); | ||
const outputLogger = new FakeOutputLogger(); | ||
|
||
await establishMetaMaskRepository({ | ||
repositoryReference: repository.name, | ||
workingDirectoryPath, | ||
cachedRepositoriesDirectoryPath, | ||
outputLogger, | ||
}); | ||
|
||
expect(execaMock).not.toHaveBeenNthCalledWith(3, 'git', ['pull'], { | ||
cwd: repository.directoryPath, | ||
}); | ||
}); | ||
}); | ||
|
||
it('returns information about the repository, even if the default branch is not selected', async () => { | ||
const fetchHeadModifiedDate = new Date('2023-01-01T00:00:00Z'); | ||
|
||
await withinSandbox(async ({ directoryPath: sandboxDirectoryPath }) => { | ||
const workingDirectoryPath = path.join(sandboxDirectoryPath, 'working'); | ||
const { cachedRepositoriesDirectoryPath, repository } = | ||
await setupToolWithMockRepository({ | ||
execaMock, | ||
sandboxDirectoryPath, | ||
repository: { | ||
name: 'some-repo', | ||
create: true, | ||
parentDirectoryPath: workingDirectoryPath, | ||
commandMocks: { | ||
'git symbolic-ref HEAD': () => ({ | ||
result: { | ||
stdout: 'refs/heads/some-branch', | ||
}, | ||
}), | ||
'git rev-parse --verify main': () => ({ | ||
error: new Error('not found'), | ||
}), | ||
'git rev-parse --verify master': () => ({ | ||
result: { | ||
stdout: '', | ||
}, | ||
}), | ||
}, | ||
fetchHead: { modifiedDate: fetchHeadModifiedDate }, | ||
}, | ||
validRepositories: [], | ||
}); | ||
const outputLogger = new FakeOutputLogger(); | ||
|
||
const metaMaskRepository = await establishMetaMaskRepository({ | ||
repositoryReference: 'some-repo', | ||
workingDirectoryPath, | ||
cachedRepositoriesDirectoryPath, | ||
outputLogger, | ||
}); | ||
|
||
expect(metaMaskRepository).toMatchObject({ | ||
shortname: 'some-repo', | ||
directoryPath: repository.directoryPath, | ||
defaultBranchName: 'master', | ||
currentBranchName: 'some-branch', | ||
lastFetchedDate: fetchHeadModifiedDate, | ||
}); | ||
}); | ||
}); | ||
}); | ||
|
||
describe('given the name of a known MetaMask repository', () => { | ||
describe('if the repository has already been cloned', () => { | ||
it('throws if the default branch is not selected', async () => { | ||
await withinSandbox(async ({ directoryPath: sandboxDirectoryPath }) => { | ||
const { cachedRepositoriesDirectoryPath, repository } = | ||
await setupToolWithMockRepository({ | ||
execaMock, | ||
sandboxDirectoryPath, | ||
repository: { | ||
create: true, | ||
commandMocks: { | ||
'git symbolic-ref HEAD': () => ({ | ||
result: { | ||
stdout: 'refs/heads/NOT-main', | ||
}, | ||
}), | ||
'git rev-parse --verify main': () => ({ | ||
result: { | ||
stdout: '', | ||
}, | ||
}), | ||
}, | ||
}, | ||
}); | ||
const outputLogger = new FakeOutputLogger(); | ||
|
||
await expect( | ||
establishMetaMaskRepository({ | ||
repositoryReference: repository.name, | ||
workingDirectoryPath: sandboxDirectoryPath, | ||
cachedRepositoriesDirectoryPath, | ||
outputLogger, | ||
}), | ||
).rejects.toThrow( | ||
`Error establishing repository "${repository.directoryPath}": The default branch "main" does not seem to be selected. You'll need to return it to this branch manually.`, | ||
); | ||
}); | ||
}); | ||
|
||
it('pulls the default branch', async () => { | ||
await withinSandbox(async ({ directoryPath: sandboxDirectoryPath }) => { | ||
const { cachedRepositoriesDirectoryPath, repository } = | ||
await setupToolWithMockRepository({ | ||
execaMock, | ||
sandboxDirectoryPath, | ||
repository: { | ||
create: true, | ||
}, | ||
}); | ||
const outputLogger = new FakeOutputLogger(); | ||
|
||
await establishMetaMaskRepository({ | ||
repositoryReference: repository.name, | ||
workingDirectoryPath: sandboxDirectoryPath, | ||
cachedRepositoriesDirectoryPath, | ||
outputLogger, | ||
}); | ||
|
||
expect(execaMock).toHaveBeenNthCalledWith(4, 'git', ['pull'], { | ||
cwd: repository.directoryPath, | ||
}); | ||
}); | ||
}); | ||
|
||
it('returns information about the repository', async () => { | ||
const now = new Date('2023-01-01T00:00:00Z'); | ||
jest.setSystemTime(now); | ||
|
||
await withinSandbox(async ({ directoryPath: sandboxDirectoryPath }) => { | ||
const { cachedRepositoriesDirectoryPath, repository } = | ||
await setupToolWithMockRepository({ | ||
execaMock, | ||
sandboxDirectoryPath, | ||
repository: { | ||
name: 'some-repo', | ||
create: true, | ||
commandMocks: { | ||
'git symbolic-ref HEAD': () => ({ | ||
result: { | ||
stdout: 'refs/heads/main', | ||
}, | ||
}), | ||
'git rev-parse --verify main': () => ({ | ||
result: { | ||
stdout: '', | ||
}, | ||
}), | ||
}, | ||
}, | ||
}); | ||
const outputLogger = new FakeOutputLogger(); | ||
|
||
const metaMaskRepository = await establishMetaMaskRepository({ | ||
repositoryReference: 'some-repo', | ||
workingDirectoryPath: sandboxDirectoryPath, | ||
cachedRepositoriesDirectoryPath, | ||
outputLogger, | ||
}); | ||
|
||
expect(metaMaskRepository).toMatchObject({ | ||
currentBranchName: 'main', | ||
defaultBranchName: 'main', | ||
directoryPath: repository.directoryPath, | ||
shortname: 'some-repo', | ||
lastFetchedDate: now, | ||
}); | ||
}); | ||
}); | ||
}); | ||
|
||
describe('if the repository has not already been cloned', () => { | ||
it('clones the repository', async () => { | ||
await withinSandbox(async ({ directoryPath: sandboxDirectoryPath }) => { | ||
const { cachedRepositoriesDirectoryPath, repository } = | ||
await setupToolWithMockRepository({ | ||
execaMock, | ||
sandboxDirectoryPath, | ||
validRepositories: [ | ||
{ | ||
name: 'some-repo', | ||
fork: false, | ||
archived: false, | ||
}, | ||
], | ||
repository: { | ||
name: 'some-repo', | ||
create: false, | ||
}, | ||
}); | ||
const outputLogger = new FakeOutputLogger(); | ||
|
||
await establishMetaMaskRepository({ | ||
repositoryReference: 'some-repo', | ||
workingDirectoryPath: sandboxDirectoryPath, | ||
cachedRepositoriesDirectoryPath, | ||
outputLogger, | ||
}); | ||
|
||
expect(execaMock).toHaveBeenNthCalledWith(2, 'gh', [ | ||
'repo', | ||
'clone', | ||
`MetaMask/some-repo`, | ||
repository.directoryPath, | ||
]); | ||
}); | ||
}); | ||
|
||
it('returns information about the repository', async () => { | ||
const now = new Date('2023-01-01T01:00:01Z'); | ||
jest.setSystemTime(now); | ||
|
||
await withinSandbox(async ({ directoryPath: sandboxDirectoryPath }) => { | ||
const { cachedRepositoriesDirectoryPath, repository } = | ||
await setupToolWithMockRepository({ | ||
execaMock, | ||
sandboxDirectoryPath, | ||
validRepositories: [ | ||
{ | ||
name: 'some-repo', | ||
fork: false, | ||
archived: false, | ||
}, | ||
], | ||
repository: { | ||
name: 'some-repo', | ||
create: false, | ||
commandMocks: { | ||
'git symbolic-ref HEAD': () => ({ | ||
result: { | ||
stdout: 'refs/heads/master', | ||
}, | ||
}), | ||
'git rev-parse --verify main': () => ({ | ||
error: new Error('not found'), | ||
}), | ||
'git rev-parse --verify master': () => ({ | ||
result: { | ||
stdout: '', | ||
}, | ||
}), | ||
}, | ||
}, | ||
}); | ||
const outputLogger = new FakeOutputLogger(); | ||
|
||
const metaMaskRepository = await establishMetaMaskRepository({ | ||
repositoryReference: 'some-repo', | ||
workingDirectoryPath: sandboxDirectoryPath, | ||
cachedRepositoriesDirectoryPath, | ||
outputLogger, | ||
}); | ||
|
||
expect(metaMaskRepository).toMatchObject({ | ||
currentBranchName: 'master', | ||
defaultBranchName: 'master', | ||
directoryPath: repository.directoryPath, | ||
shortname: 'some-repo', | ||
lastFetchedDate: now, | ||
}); | ||
}); | ||
}); | ||
}); | ||
}); | ||
}); |
Oops, something went wrong.