@opentermsarchive/engine 16.0.2 → 16.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (51) hide show
  1. package/config/default.json +13 -0
  2. package/config/test.json +12 -0
  3. package/package.json +1 -1
  4. package/scripts/declarations/validate/index.mocha.js +7 -0
  5. package/scripts/import/index.js +1 -1
  6. package/scripts/import/loadCommits.js +1 -1
  7. package/scripts/rewrite/initializer/index.js +1 -1
  8. package/scripts/rewrite/rewrite-snapshots.js +1 -1
  9. package/scripts/rewrite/rewrite-versions.js +1 -1
  10. package/src/archivist/fetcher/htmlOnlyFetcher.js +8 -0
  11. package/src/archivist/fetcher/index.test.js +12 -0
  12. package/src/archivist/index.js +106 -23
  13. package/src/archivist/index.test.js +659 -9
  14. package/src/archivist/recorder/repositories/git/dataMapper.js +2 -1
  15. package/src/archivist/recorder/repositories/git/index.js +2 -13
  16. package/src/archivist/recorder/repositories/git/index.test.js +23 -1
  17. package/src/archivist/services/index.js +45 -1
  18. package/src/archivist/services/index.test.js +52 -1
  19. package/src/archivist/services/sourceDocument.js +8 -2
  20. package/src/archivist/services/sourceDocument.test.js +37 -0
  21. package/src/archivist/tracking-results/errors.js +5 -0
  22. package/src/archivist/tracking-results/index.js +233 -0
  23. package/src/archivist/tracking-results/index.test.js +406 -0
  24. package/src/archivist/tracking-results/recorder.js +156 -0
  25. package/src/archivist/tracking-results/recorder.test.js +559 -0
  26. package/src/archivist/tracking-results/repository.js +136 -0
  27. package/src/archivist/tracking-results/repository.test.js +763 -0
  28. package/src/archivist/tracking-results/run/dataMapper.js +51 -0
  29. package/src/archivist/tracking-results/run/dataMapper.test.js +168 -0
  30. package/src/archivist/tracking-results/run/index.js +115 -0
  31. package/src/archivist/tracking-results/run/index.test.js +221 -0
  32. package/src/archivist/tracking-results/terms-result/dataMapper.js +200 -0
  33. package/src/archivist/tracking-results/terms-result/dataMapper.test.js +575 -0
  34. package/src/archivist/tracking-results/terms-result/index.js +42 -0
  35. package/src/archivist/tracking-results/terms-result/index.test.js +228 -0
  36. package/src/git/errors.js +1 -0
  37. package/src/{archivist/recorder/repositories/git/git.js → git/index.js} +59 -9
  38. package/src/git/index.test.js +266 -0
  39. package/src/git/pathSegment.js +18 -0
  40. package/src/git/pathSegment.test.js +33 -0
  41. package/src/index.js +5 -4
  42. package/src/reporter/factory.js +2 -2
  43. package/src/reporter/github/index.js +7 -5
  44. package/src/reporter/github/index.test.js +16 -1
  45. package/src/reporter/gitlab/index.js +6 -6
  46. package/src/reporter/gitlab/index.test.js +24 -1
  47. package/src/reporter/index.js +9 -4
  48. package/src/reporter/index.test.js +74 -9
  49. package/src/archivist/recorder/repositories/git/git.test.js +0 -114
  50. /package/src/{archivist/recorder/repositories/git → git}/trailers.js +0 -0
  51. /package/src/{archivist/recorder/repositories/git → git}/trailers.test.js +0 -0
package/src/index.js CHANGED
@@ -12,10 +12,11 @@ import Reporter from './reporter/index.js';
12
12
  const require = createRequire(import.meta.url);
13
13
  const { version: PACKAGE_VERSION } = require('../package.json');
14
14
 
15
- async function initialize(services) {
15
+ async function initialize(services, { trackingResultsConfig } = {}) {
16
16
  const archivist = new Archivist({
17
17
  recorderConfig: config.get('@opentermsarchive/engine.recorder'),
18
18
  fetcherConfig: config.get('@opentermsarchive/engine.fetcher'),
19
+ trackingResultsConfig,
19
20
  });
20
21
 
21
22
  archivist.attach(logger);
@@ -28,7 +29,7 @@ async function initialize(services) {
28
29
  logger.info(`Start engine v${PACKAGE_VERSION}${collectionName}\n`);
29
30
 
30
31
  if (services?.length) {
31
- services = services.filter(serviceId => {
32
+ services = [...new Set(services)].filter(serviceId => { // Deduplicated so that a service ID given twice is not tracked twice in the same run
32
33
  const isServiceDeclared = archivist.services[serviceId];
33
34
 
34
35
  if (!isServiceDeclared) {
@@ -43,7 +44,7 @@ async function initialize(services) {
43
44
  }
44
45
 
45
46
  export default async function track({ services, types, schedule }) {
46
- const { archivist, services: filteredServices } = await initialize(services);
47
+ const { archivist, services: filteredServices } = await initialize(services, { trackingResultsConfig: config.get('@opentermsarchive/engine.tracking-results') });
47
48
 
48
49
  // Technical upgrade pass: apply changes from engine, dependency, or declaration upgrades.
49
50
  // This regenerates versions from existing snapshots with updated extraction logic.
@@ -84,7 +85,7 @@ export default async function track({ services, types, schedule }) {
84
85
  }
85
86
 
86
87
  export async function applyTechnicalUpgrades({ services, types }) {
87
- const { archivist, services: filteredServices } = await initialize(services);
88
+ const { archivist, services: filteredServices } = await initialize(services); // Without tracking-results: technical upgrades never record any, and initializing the module would finalize as crashed the run that a tracking process may have in progress
88
89
 
89
90
  await archivist.applyTechnicalUpgrades({ services: filteredServices, types });
90
91
  }
@@ -4,9 +4,9 @@ import GitLab from './gitlab/index.js';
4
4
  export function createReporter(config) {
5
5
  switch (config.type) {
6
6
  case 'github':
7
- return new GitHub(config.repositories.declarations);
7
+ return new GitHub(config.repositories);
8
8
  case 'gitlab':
9
- return new GitLab(config.repositories.declarations, config.baseURL, config.apiBaseURL);
9
+ return new GitLab(config.repositories, config.baseURL, config.apiBaseURL);
10
10
  default:
11
11
  throw new Error(`Unsupported reporter type: ${config.type}`);
12
12
  }
@@ -13,7 +13,7 @@ export default class GitHub {
13
13
  static ISSUE_STATE_ALL = 'all';
14
14
  static MAX_LABEL_DESCRIPTION_LENGTH = 100;
15
15
 
16
- constructor(repository) {
16
+ constructor(repositories) {
17
17
  const { version } = require('../../../package.json');
18
18
 
19
19
  this.octokit = new Octokit({
@@ -25,7 +25,9 @@ export default class GitHub {
25
25
  },
26
26
  });
27
27
 
28
- const [ owner, repo ] = repository.split('/');
28
+ this.repositories = repositories; // Each generated link points into its own repository, while the issues live in the declarations one
29
+
30
+ const [ owner, repo ] = repositories.declarations.split('/');
29
31
 
30
32
  this.commonParams = { owner, repo };
31
33
 
@@ -272,14 +274,14 @@ export default class GitHub {
272
274
  }
273
275
 
274
276
  generateDeclarationURL(serviceName) {
275
- return `https://github.com/${this.commonParams.owner}/${this.commonParams.repo}/blob/main/declarations/${encodeURIComponent(serviceName)}.json`;
277
+ return `https://github.com/${this.repositories.declarations}/blob/main/declarations/${encodeURIComponent(serviceName)}.json`;
276
278
  }
277
279
 
278
280
  generateVersionURL(serviceName, termsType) {
279
- return `https://github.com/${this.commonParams.owner}/${this.commonParams.repo}/blob/main/${encodeURIComponent(serviceName)}/${encodeURIComponent(termsType)}.md`;
281
+ return `https://github.com/${this.repositories.versions}/blob/main/${encodeURIComponent(serviceName)}/${encodeURIComponent(termsType)}.md`;
280
282
  }
281
283
 
282
284
  generateSnapshotsBaseUrl(serviceName, termsType) {
283
- return `https://github.com/${this.commonParams.owner}/${this.commonParams.repo}/blob/main/${encodeURIComponent(serviceName)}/${encodeURIComponent(termsType)}`;
285
+ return `https://github.com/${this.repositories.snapshots}/blob/main/${encodeURIComponent(serviceName)}/${encodeURIComponent(termsType)}`;
284
286
  }
285
287
  }
@@ -10,12 +10,13 @@ describe('GitHub', function () {
10
10
 
11
11
  let MANAGED_LABELS;
12
12
  let github;
13
+ const REPOSITORIES = { declarations: 'owner/repo', versions: 'owner/versions-repo', snapshots: 'owner/snapshots-repo' };
13
14
  const EXISTING_OPEN_ISSUE = { number: 1, title: 'Opened issue', description: 'Issue description', state: GitHub.ISSUE_STATE_OPEN, labels: [{ name: LABELS.HTTP_403.name }] };
14
15
  const EXISTING_CLOSED_ISSUE = { number: 2, title: 'Closed issue', description: 'Issue description', state: GitHub.ISSUE_STATE_CLOSED, labels: [{ name: LABELS.EMPTY_CONTENT.name }] };
15
16
 
16
17
  before(async () => {
17
18
  MANAGED_LABELS = Object.values(LABELS);
18
- github = new GitHub('owner/repo');
19
+ github = new GitHub(REPOSITORIES);
19
20
  nock('https://api.github.com')
20
21
  .get('/repos/owner/repo/issues')
21
22
  .query(true)
@@ -518,4 +519,18 @@ describe('GitHub', function () {
518
519
  });
519
520
  });
520
521
  });
522
+
523
+ describe('URL generation', () => {
524
+ it('links the declaration into the declarations repository', () => {
525
+ expect(github.generateDeclarationURL('Service A')).to.equal('https://github.com/owner/repo/blob/main/declarations/Service%20A.json');
526
+ });
527
+
528
+ it('links the version into the versions repository', () => {
529
+ expect(github.generateVersionURL('Service A', 'Terms of Service')).to.equal('https://github.com/owner/versions-repo/blob/main/Service%20A/Terms%20of%20Service.md');
530
+ });
531
+
532
+ it('links the snapshots into the snapshots repository', () => {
533
+ expect(github.generateSnapshotsBaseUrl('Service A', 'Terms of Service')).to.equal('https://github.com/owner/snapshots-repo/blob/main/Service%20A/Terms%20of%20Service');
534
+ });
535
+ });
521
536
  });
@@ -14,8 +14,8 @@ export default class GitLab {
14
14
  static ISSUE_STATE_ALL = 'all';
15
15
  static MAX_LABEL_DESCRIPTION_LENGTH = 255;
16
16
 
17
- constructor(repository, baseURL = BASE_URL, apiBaseURL = API_BASE_URL) {
18
- this.repositoryPath = repository;
17
+ constructor(repositories, baseURL = BASE_URL, apiBaseURL = API_BASE_URL) {
18
+ this.repositories = repositories; // Each generated link points into its own repository, while the issues live in the declarations one
19
19
  this.projectId = null;
20
20
  this.baseURL = baseURL;
21
21
  this.apiBaseURL = apiBaseURL;
@@ -26,7 +26,7 @@ export default class GitLab {
26
26
 
27
27
  try {
28
28
  const response = await nodeFetch(
29
- `${this.apiBaseURL}/projects/${encodeURIComponent(this.repositoryPath)}`,
29
+ `${this.apiBaseURL}/projects/${encodeURIComponent(this.repositories.declarations)}`,
30
30
  options,
31
31
  );
32
32
 
@@ -478,15 +478,15 @@ export default class GitLab {
478
478
  }
479
479
 
480
480
  generateDeclarationURL(serviceName) {
481
- return `${this.baseURL}/${this.repositoryPath}/-/blob/main/declarations/${encodeURIComponent(serviceName)}.json`;
481
+ return `${this.baseURL}/${this.repositories.declarations}/-/blob/main/declarations/${encodeURIComponent(serviceName)}.json`;
482
482
  }
483
483
 
484
484
  generateVersionURL(serviceName, termsType) {
485
- return `${this.baseURL}/${this.repositoryPath}/-/blob/main/${encodeURIComponent(serviceName)}/${encodeURIComponent(termsType)}.md`;
485
+ return `${this.baseURL}/${this.repositories.versions}/-/blob/main/${encodeURIComponent(serviceName)}/${encodeURIComponent(termsType)}.md`;
486
486
  }
487
487
 
488
488
  generateSnapshotsBaseUrl(serviceName, termsType) {
489
- return `${this.baseURL}/${this.repositoryPath}/-/blob/main/${encodeURIComponent(serviceName)}/${encodeURIComponent(termsType)}`;
489
+ return `${this.baseURL}/${this.repositories.snapshots}/-/blob/main/${encodeURIComponent(serviceName)}/${encodeURIComponent(termsType)}`;
490
490
  }
491
491
 
492
492
  // GitLab API responses are not cached unlike GitHub, so this method only exists to satisfy the Reporter interface contract
@@ -11,10 +11,11 @@ describe('GitLab', function () {
11
11
  let MANAGED_LABELS;
12
12
  let gitlab;
13
13
  const PROJECT_ID = '4';
14
+ const REPOSITORIES = { declarations: 'owner/repo', versions: 'owner/versions-repo', snapshots: 'owner/snapshots-repo' };
14
15
 
15
16
  before(() => {
16
17
  MANAGED_LABELS = Object.values(LABELS);
17
- gitlab = new GitLab('owner/repo');
18
+ gitlab = new GitLab(REPOSITORIES);
18
19
  });
19
20
 
20
21
  describe('#initialize', () => {
@@ -767,4 +768,26 @@ describe('GitLab', function () {
767
768
  });
768
769
  });
769
770
  });
771
+
772
+ describe('URL generation', () => {
773
+ it('links the declaration into the declarations repository', () => {
774
+ expect(gitlab.generateDeclarationURL('Service A')).to.equal('https://gitlab.com/owner/repo/-/blob/main/declarations/Service%20A.json');
775
+ });
776
+
777
+ it('links the version into the versions repository', () => {
778
+ expect(gitlab.generateVersionURL('Service A', 'Terms of Service')).to.equal('https://gitlab.com/owner/versions-repo/-/blob/main/Service%20A/Terms%20of%20Service.md');
779
+ });
780
+
781
+ it('links the snapshots into the snapshots repository', () => {
782
+ expect(gitlab.generateSnapshotsBaseUrl('Service A', 'Terms of Service')).to.equal('https://gitlab.com/owner/snapshots-repo/-/blob/main/Service%20A/Terms%20of%20Service');
783
+ });
784
+
785
+ context('with a custom base URL', () => {
786
+ it('links into the custom instance', () => {
787
+ const customGitlab = new GitLab(REPOSITORIES, 'https://gitlab.example.test');
788
+
789
+ expect(customGitlab.generateVersionURL('Service A', 'Terms of Service')).to.equal('https://gitlab.example.test/owner/versions-repo/-/blob/main/Service%20A/Terms%20of%20Service.md');
790
+ });
791
+ });
792
+ });
770
793
  });
@@ -144,9 +144,14 @@ No changes were found in the last run, so no new version has been recorded.`,
144
144
  const latestDeclarationLink = `[Latest declaration](${declarationFileUrl})`;
145
145
  const latestVersionLink = `[Latest version](${this.reporter.generateVersionURL(terms.service.name, terms.type)})`;
146
146
  const snapshotsBaseUrl = this.reporter.generateSnapshotsBaseUrl(terms.service.name, terms.type);
147
- const latestSnapshotsLink = terms.hasMultipleSourceDocuments
148
- ? `Latest snapshots:\n - ${terms.sourceDocuments.map(sourceDocument => `[${sourceDocument.id}](${snapshotsBaseUrl}.%20#${sourceDocument.id}.${mime.getExtension(sourceDocument.mimeType)})`).join('\n - ')}`
149
- : `[Latest snapshot](${snapshotsBaseUrl}.${mime.getExtension(terms.sourceDocuments[0].mimeType)})`;
147
+ const recordedSourceDocuments = terms.sourceDocuments.filter(sourceDocument => sourceDocument.snapshotId); // A source document that has never been recorded has no snapshot file to link to, and its unknown MIME type would produce a link to a nonexistent ".null" file
148
+ let latestSnapshotsLink = '';
149
+
150
+ if (recordedSourceDocuments.length) {
151
+ latestSnapshotsLink = terms.hasMultipleSourceDocuments
152
+ ? `Latest snapshots:\n - ${recordedSourceDocuments.map(sourceDocument => `[${sourceDocument.id}](${snapshotsBaseUrl}.%20#${sourceDocument.id}.${mime.getExtension(sourceDocument.mimeType)})`).join('\n - ')}`
153
+ : `[Latest snapshot](${snapshotsBaseUrl}.${mime.getExtension(recordedSourceDocuments[0].mimeType)})`;
154
+ }
150
155
 
151
156
  /* eslint-disable no-irregular-whitespace */
152
157
  return `
@@ -185,7 +190,7 @@ If the source documents are accessible in a browser but fetching them always fai
185
190
 
186
191
  - ${latestDeclarationLink}
187
192
  ${this.repositories.versions ? `- ${latestVersionLink}` : ''}
188
- ${this.repositories.snapshots ? `- ${latestSnapshotsLink}` : ''}
193
+ ${this.repositories.snapshots && latestSnapshotsLink ? `- ${latestSnapshotsLink}` : ''}
189
194
  `;
190
195
  /* eslint-enable no-irregular-whitespace */
191
196
  }
@@ -64,17 +64,21 @@ describe('Reporter', () => {
64
64
  describe('#generateDescription', () => {
65
65
  const buildReporter = () => new Reporter({
66
66
  type: 'github',
67
- repositories: { declarations: 'OpenTermsArchive/test-declarations' },
67
+ repositories: { declarations: 'OpenTermsArchive/test-declarations', versions: 'OpenTermsArchive/test-versions', snapshots: 'OpenTermsArchive/test-snapshots' },
68
68
  });
69
69
 
70
- const buildTerms = ({ sourceCount = 1 } = {}) => {
71
- const sourceDocuments = Array.from({ length: sourceCount }, (_, index) => ({
72
- id: `source-${index}`,
73
- location: `https://example.com/source-${index}`,
74
- mimeType: 'text/html',
75
- snapshotId: `snapshot-${index}`,
76
- toPersistence: () => ({ fetch: `https://example.com/source-${index}` }),
77
- }));
70
+ const buildTerms = ({ sourceCount = 1, hasSnapshots = true, withoutSnapshotIndexes = [] } = {}) => {
71
+ const sourceDocuments = Array.from({ length: sourceCount }, (_, index) => {
72
+ const hasSnapshot = hasSnapshots && !withoutSnapshotIndexes.includes(index); // A document that has never been recorded has neither a snapshot ID nor an observed MIME type
73
+
74
+ return {
75
+ id: `source-${index}`,
76
+ location: `https://example.com/source-${index}`,
77
+ mimeType: hasSnapshot ? 'text/html' : null,
78
+ snapshotId: hasSnapshot ? `snapshot-${index}` : null,
79
+ toPersistence: () => ({ fetch: `https://example.com/source-${index}` }),
80
+ };
81
+ });
78
82
 
79
83
  return {
80
84
  service: { id: 'TestService', name: 'TestService' },
@@ -94,6 +98,40 @@ describe('Reporter', () => {
94
98
 
95
99
  const error = { reasons: ['HTTP code 404'] };
96
100
 
101
+ context('when the source documents have been recorded as snapshots', () => {
102
+ it('mentions that the missed versions might be recovered', () => {
103
+ const description = buildReporter().generateDescription({ error, terms: buildTerms() });
104
+
105
+ expect(description).to.include('it might still be possible to recover the missed versions');
106
+ });
107
+ });
108
+
109
+ context('when the source documents could not be recorded as snapshots', () => {
110
+ it('does not suggest that the missed versions might be recovered', () => {
111
+ const description = buildReporter().generateDescription({ error, terms: buildTerms({ hasSnapshots: false }) });
112
+
113
+ expect(description).to.include('has not been recorded as a snapshot');
114
+ expect(description).to.not.include('it might still be possible to recover the missed versions');
115
+ });
116
+
117
+ it('omits the snapshot link, which would point to a nonexistent file', () => {
118
+ const description = buildReporter().generateDescription({ error, terms: buildTerms({ hasSnapshots: false }) });
119
+
120
+ expect(description).to.not.include('Latest snapshot');
121
+ expect(description).to.not.include('.null');
122
+ });
123
+ });
124
+
125
+ context('when a source document of combined terms has never been recorded as a snapshot', () => {
126
+ it('omits its snapshot link and keeps the ones of the recorded documents', () => {
127
+ const description = buildReporter().generateDescription({ error, terms: buildTerms({ sourceCount: 2, withoutSnapshotIndexes: [1] }) });
128
+
129
+ expect(description).to.include('#source-0.html'); // The `#<id>` fragment only appears in snapshot links, unlike the document locations listed in the accessibility checklist
130
+ expect(description).to.not.include('#source-1');
131
+ expect(description).to.not.include('.null');
132
+ });
133
+ });
134
+
97
135
  context('when the terms has a single source document', () => {
98
136
  it('deep-links to the contribution tool with the serialized declaration as the edit target', () => {
99
137
  const description = buildReporter().generateDescription({ error, terms: buildTerms({ sourceCount: 1 }) });
@@ -121,5 +159,32 @@ describe('Reporter', () => {
121
159
  expect(description.length).to.be.lessThan(65000);
122
160
  });
123
161
  });
162
+
163
+ context('references links', () => {
164
+ it('links the latest version into the versions repository', () => {
165
+ const description = buildReporter().generateDescription({ error, terms: buildTerms() });
166
+
167
+ expect(description).to.include('github.com/OpenTermsArchive/test-versions/blob/main/TestService/Terms%20of%20Service.md');
168
+ });
169
+
170
+ it('links the latest snapshots into the snapshots repository', () => {
171
+ const description = buildReporter().generateDescription({ error, terms: buildTerms() });
172
+
173
+ expect(description).to.include('github.com/OpenTermsArchive/test-snapshots/blob/main/TestService/Terms%20of%20Service');
174
+ expect(description).to.not.include('test-declarations/blob/main/TestService/Terms%20of%20Service'); // A version or snapshot link into the declarations repository is the regression this guards against
175
+ });
176
+
177
+ context('when only the declarations repository is configured', () => {
178
+ it('omits the version and snapshots links instead of generating broken ones', () => {
179
+ const reporter = new Reporter({ type: 'github', repositories: { declarations: 'OpenTermsArchive/test-declarations' } });
180
+
181
+ const description = reporter.generateDescription({ error, terms: buildTerms() });
182
+
183
+ expect(description).to.not.include('Latest version');
184
+ expect(description).to.not.include('Latest snapshot');
185
+ expect(description).to.not.include('undefined');
186
+ });
187
+ });
188
+ });
124
189
  });
125
190
  });
@@ -1,114 +0,0 @@
1
- import fs from 'fs/promises';
2
- import path from 'path';
3
- import { fileURLToPath } from 'url';
4
-
5
- import { expect } from 'chai';
6
- import config from 'config';
7
-
8
- import Git from './git.js';
9
-
10
- const __dirname = path.dirname(fileURLToPath(import.meta.url));
11
- const RECORDER_PATH = path.resolve(__dirname, '../../../../../', config.get('@opentermsarchive/engine.recorder.versions.storage.git.path'));
12
-
13
- describe('Git', () => {
14
- const DEFAULT_CONTENT = 'default content';
15
- const DEFAULT_COMMIT_MESSAGE = 'default commit message';
16
- let subject;
17
-
18
- before(() => {
19
- subject = new Git({
20
- path: RECORDER_PATH,
21
- author: {
22
- name: config.get('@opentermsarchive/engine.recorder.versions.storage.git.author.name'),
23
- email: config.get('@opentermsarchive/engine.recorder.versions.storage.git.author.email'),
24
- },
25
- });
26
-
27
- return subject.initialize();
28
- });
29
-
30
- describe('#commit', () => {
31
- const expectedFilePath = `${RECORDER_PATH}/test.md`;
32
-
33
- let commitId;
34
-
35
- before(async () => {
36
- await fs.writeFile(expectedFilePath, DEFAULT_CONTENT);
37
-
38
- await subject.add(expectedFilePath);
39
- commitId = await subject.commit({ filePath: expectedFilePath, message: DEFAULT_COMMIT_MESSAGE });
40
- });
41
-
42
- after(() => subject.destroyHistory());
43
-
44
- it('returns a full length SHA1 commit ID', () => {
45
- expect(commitId).to.match(/\b[0-9a-f]{40}\b/);
46
- });
47
-
48
- context('when stage area is dirty', () => {
49
- const expectedFileName = 'file-to-commit.md';
50
- const expectedFilePath = `${RECORDER_PATH}/${expectedFileName}`;
51
- const unwantedFilePath = `${RECORDER_PATH}/unwanted-file.md`;
52
- let commit;
53
- let committedFiles;
54
- let committedFileName;
55
-
56
- before(async () => {
57
- await fs.writeFile(expectedFilePath, DEFAULT_CONTENT);
58
- await fs.writeFile(unwantedFilePath, DEFAULT_CONTENT);
59
-
60
- await subject.add(expectedFilePath);
61
- await subject.add(unwantedFilePath);
62
-
63
- const commitId = await subject.commit({ filePath: expectedFilePath, message: DEFAULT_COMMIT_MESSAGE });
64
-
65
- commit = await subject.getCommit([commitId]);
66
-
67
- if (!commit) {
68
- return;
69
- }
70
-
71
- ({ files: committedFiles } = commit.diff);
72
- ([{ file: committedFileName }] = committedFiles);
73
- });
74
-
75
- after(() => subject.destroyHistory());
76
-
77
- it('commits the specified file', () => {
78
- expect(committedFileName).to.equal(expectedFileName);
79
- });
80
-
81
- it('commits only one file', () => {
82
- expect(committedFiles).to.have.lengthOf(1);
83
- });
84
- });
85
- });
86
-
87
- describe('#cleanUp', () => {
88
- context('when a commit-graph lock has been left behind by an interrupted process', () => {
89
- const infoDirectoryPath = path.join(RECORDER_PATH, '.git', 'objects', 'info');
90
- const lockFilePath = path.join(infoDirectoryPath, 'commit-graph.lock');
91
-
92
- before(async () => {
93
- const filePath = `${RECORDER_PATH}/file-to-clean.md`;
94
-
95
- await fs.writeFile(filePath, DEFAULT_CONTENT);
96
- await subject.add(filePath);
97
- await subject.commit({ filePath, message: DEFAULT_COMMIT_MESSAGE });
98
-
99
- await fs.mkdir(infoDirectoryPath, { recursive: true });
100
- await fs.writeFile(lockFilePath, '');
101
-
102
- await subject.cleanUp();
103
- });
104
-
105
- after(() => subject.destroyHistory());
106
-
107
- it('removes the stale commit-graph lock', async () => {
108
- const infoDirectoryContent = await fs.readdir(infoDirectoryPath);
109
-
110
- expect(infoDirectoryContent).to.not.include('commit-graph.lock');
111
- });
112
- });
113
- });
114
- });