aboutsummaryrefslogtreecommitdiffhomepage
path: root/test/util.ts
blob: cb689c3af5d10317f998e6bf559f753e7f1c1392 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
import crypto from 'node:crypto';
import { expect, jest } from '@jest/globals';
import type { DeepMockProxy } from 'jest-mock-extended';
import type { Plugin } from 'pretty-format';
import upath from 'upath';
import type { RenovateConfig } from '../lib/config/types';
import * as _logger from '../lib/logger';
import type { Platform } from '../lib/modules/platform';
import { platform as _platform } from '../lib/modules/platform';
import { scm as _scm } from '../lib/modules/platform/scm';
import * as _env from '../lib/util/exec/env';
import * as _fs from '../lib/util/fs';
import * as _git from '../lib/util/git';
import * as _hostRules from '../lib/util/host-rules';
import { regEx } from '../lib/util/regex';

/**
 * Simple wrapper for getting mocked version of a module
 * @param module module which is mocked by `jest.mock`
 */
export function mocked<T extends object>(module: T): jest.Mocked<T> {
  return jest.mocked(module);
}

/**
 * Simple wrapper for getting mocked version of a module
 * @param module module which is mocked by `jest-mock-extended.mockDeep`
 */
export function mockedExtended<T extends object>(module: T): DeepMockProxy<T> {
  return module as DeepMockProxy<T>;
}

/**
 * Simple wrapper for getting mocked version of a function
 * @param func function which is mocked by `jest.mock`
 */
export function mockedFunction<T extends (...args: any[]) => any>(
  func: T,
): jest.MockedFunction<T> {
  return func as jest.MockedFunction<T>;
}

/**
 * Simply wrapper to create partial mocks.
 * @param obj Object to cast to final type
 */
export function partial<T>(): T;
export function partial<T>(obj: Partial<T>): T;
export function partial<T>(obj: Partial<T>[]): T[];
export function partial(obj: unknown = {}): unknown {
  return obj;
}

export const fs = jest.mocked(_fs);
export const git = jest.mocked(_git);

// TODO: fix types, jest / typescript is using wrong overload (#22198)
export const platform = jest.mocked(partial<Required<Platform>>(_platform));
export const scm = jest.mocked(_scm);
export const env = jest.mocked(_env);
export const hostRules = jest.mocked(_hostRules);
export const logger = jest.mocked(_logger);

export type { RenovateConfig };

function getCallerFileName(): string | null {
  let result: string | null = null;

  const prepareStackTrace = Error.prepareStackTrace;
  const stackTraceLimit = Error.stackTraceLimit;

  Error.prepareStackTrace = (_err, stack) => stack;
  Error.stackTraceLimit = 5; // max calls inside this file + 1

  try {
    const err = new Error();

    const stack = err.stack as unknown as NodeJS.CallSite[];

    let currentFile: string | null = null;
    for (const frame of stack) {
      const fileName = frame.getFileName() ?? null;
      if (!currentFile) {
        currentFile = fileName;
      } else if (currentFile !== fileName) {
        result = fileName;
        break;
      }
    }
  } catch {
    // no-op
  }

  Error.prepareStackTrace = prepareStackTrace;
  Error.stackTraceLimit = stackTraceLimit;

  return result;
}

export function getFixturePath(fixtureFile: string, fixtureRoot = '.'): string {
  const callerDir = upath.dirname(getCallerFileName()!);
  return upath.join(callerDir, fixtureRoot, '__fixtures__', fixtureFile);
}

/**
 * Can be used to search and replace strings in jest snapshots.
 * @example
 * expect.addSnapshotSerializer(
 *     replacingSerializer(upath.toUnix(gradleDir.path), 'localDir')
 * );
 */
export const replacingSerializer = (
  search: string,
  replacement: string,
): Plugin => ({
  test: (value) => typeof value === 'string' && value.includes(search),
  serialize: (val, config, indent, depth, refs, printer) => {
    const replaced = (val as string).replace(search, replacement);
    return printer(replaced, config, indent, depth, refs);
  },
});

export function addReplacingSerializer(from: string, to: string): void {
  expect.addSnapshotSerializer(replacingSerializer(from, to));
}

function toHash(buf: Buffer): string {
  return crypto.createHash('sha256').update(buf).digest('hex');
}

const bufferSerializer: Plugin = {
  test: (value) => Buffer.isBuffer(value),
  serialize: (val, config, indent, depth, refs, printer) => {
    const replaced = toHash(val);
    return printer(replaced, config, indent, depth, refs);
  },
};

export function addBufferSerializer(): void {
  expect.addSnapshotSerializer(bufferSerializer);
}

export function regexMatches(target: string, patterns: string[]): boolean {
  return patterns.some((patt: string) => {
    const re = regEx(patt);
    return re.test(target);
  });
}