Compare commits

..

2 Commits

Author SHA1 Message Date
copilot-swe-agent[bot] 7d647bdf47 Update CODEOWNERS to Azure/cloud-native-github-action-owners
Co-authored-by: davidgamero <4248857+davidgamero@users.noreply.github.com>
2025-07-16 02:24:37 +00:00
copilot-swe-agent[bot] f76da0c3d8 Initial plan 2025-07-16 02:18:34 +00:00
25 changed files with 9931 additions and 2352 deletions
+3 -3
View File
@@ -55,11 +55,11 @@ jobs:
# your codebase is analyzed, see https://docs.github.com/en/code-security/code-scanning/creating-an-advanced-setup-for-code-scanning/codeql-code-scanning-for-compiled-languages # your codebase is analyzed, see https://docs.github.com/en/code-security/code-scanning/creating-an-advanced-setup-for-code-scanning/codeql-code-scanning-for-compiled-languages
steps: steps:
- name: Checkout repository - name: Checkout repository
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
# Initializes the CodeQL tools for scanning. # Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL - name: Initialize CodeQL
uses: github/codeql-action/init@89a39a4e59826350b863aa6b6252a07ad50cf83e # v3.29.5 uses: github/codeql-action/init@181d5eefc20863364f96762470ba6f862bdef56b # v3.29.2
with: with:
languages: ${{ matrix.language }} languages: ${{ matrix.language }}
build-mode: ${{ matrix.build-mode }} build-mode: ${{ matrix.build-mode }}
@@ -86,6 +86,6 @@ jobs:
echo ' make release' echo ' make release'
exit 1 exit 1
- name: Perform CodeQL Analysis - name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@89a39a4e59826350b863aa6b6252a07ad50cf83e # v3.29.5 uses: github/codeql-action/analyze@181d5eefc20863364f96762470ba6f862bdef56b # v3.29.2
with: with:
category: '/language:${{matrix.language}}' category: '/language:${{matrix.language}}'
+2 -2
View File
@@ -13,7 +13,7 @@ jobs:
# Steps represent a sequence of tasks that will be executed as part of the job # Steps represent a sequence of tasks that will be executed as part of the job
steps: steps:
- uses: actions/stale@b5d41d4e1d5dceea10e7104786b73624c18a190f # v10.2.0 - uses: actions/stale@5bef64f19d7facfb25b37b414482c7164d639639 # v9.1.0
name: Setting Issue as Idle name: Setting Issue as Idle
with: with:
repo-token: ${{ secrets.GITHUB_TOKEN }} repo-token: ${{ secrets.GITHUB_TOKEN }}
@@ -24,7 +24,7 @@ jobs:
operations-per-run: 100 operations-per-run: 100
exempt-issue-labels: 'backlog' exempt-issue-labels: 'backlog'
- uses: actions/stale@b5d41d4e1d5dceea10e7104786b73624c18a190f # v10.2.0 - uses: actions/stale@5bef64f19d7facfb25b37b414482c7164d639639 # v9.1.0
name: Setting PR as Idle name: Setting PR as Idle
with: with:
repo-token: ${{ secrets.GITHUB_TOKEN }} repo-token: ${{ secrets.GITHUB_TOKEN }}
+1 -1
View File
@@ -15,7 +15,7 @@ jobs:
steps: steps:
- name: Checkout Source Code - name: Checkout Source Code
id: checkout-code id: checkout-code
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- name: Npm Install and Build - name: Npm Install and Build
id: npm-build id: npm-build
run: | run: |
+2 -2
View File
@@ -10,10 +10,10 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Checkout Repository - name: Checkout Repository
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- name: Setup Node.js - name: Setup Node.js
uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0 uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
with: with:
node-version: 'lts/*' node-version: 'lts/*'
cache: 'npm' cache: 'npm'
+1 -1
View File
@@ -13,7 +13,7 @@ jobs:
unit-test: unit-test:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- name: Run Unit Tests - name: Run Unit Tests
run: | run: |
npm install npm install
-5
View File
@@ -1,10 +1,5 @@
# Change Log # Change Log
## [4.0.2] - 2025-11-13
- #[164](https://github.com/Azure/k8s-set-context/pull/164) Adding optional kubeconfig encoding variable
- Dependabot updates
## [4.0.1] - 2024-09-06 ## [4.0.1] - 2024-09-06
- #90 update dev dependencies with Typescript to 5 - #90 update dev dependencies with Typescript to 5
-4
View File
@@ -107,7 +107,3 @@ provided by the bot. You will only need to do this once across all repos using o
This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/). This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/).
For more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or For more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or
contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments. contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments.
### Local development
Use `npm run build` to bundle the action with esbuild (output is `lib/index.cjs`, referenced by `action.yml`) and `npm run test` to execute the Vitest suite, including an integration test that runs the built action.
+1 -5
View File
@@ -13,10 +13,6 @@ inputs:
kubeconfig: kubeconfig:
description: 'Contents of kubeconfig file' description: 'Contents of kubeconfig file'
required: false required: false
kubeconfig-encoding:
description: 'Encoding of the kubeconfig input. Accepts "plaintext" (default) or "base64".'
required: false
default: 'plaintext'
context: context:
description: 'If your kubeconfig has multiple contexts, use this field to use a specific context, otherwise the default one would be chosen' description: 'If your kubeconfig has multiple contexts, use this field to use a specific context, otherwise the default one would be chosen'
required: false required: false
@@ -40,4 +36,4 @@ branding:
color: 'blue' color: 'blue'
runs: runs:
using: 'node20' using: 'node20'
main: 'lib/index.cjs' main: 'lib/index.js'
+7
View File
@@ -0,0 +1,7 @@
// babel.config.js
export default {
presets: [
'@babel/preset-env', // For handling ES modules
'@babel/preset-typescript' // For handling TypeScript
]
}
+28
View File
@@ -0,0 +1,28 @@
export default {
restoreMocks: true,
clearMocks: true,
resetMocks: true,
moduleFileExtensions: ['js', 'ts'],
testEnvironment: 'node',
testMatch: ['**/*.test.ts'],
transform: {
'^.+\\.ts$': 'ts-jest', // Use ts-jest for TypeScript files
'^.+\\.js$': 'babel-jest' // Transform TypeScript files
},
transformIgnorePatterns: [
'/node_modules/(?!@kubernetes/client-node)/' // Make sure to transform the Kubernetes client module
],
moduleNameMapper: {
'^.+\\.css$': 'jest-transform-stub' // Handle CSS imports (if any)
},
extensionsToTreatAsEsm: ['.ts', '.tsx'], // Treat TypeScript files as ESM
verbose: true,
coverageThreshold: {
global: {
branches: 0,
functions: 40,
lines: 22,
statements: 22
}
}
}
+9838 -2119
View File
File diff suppressed because it is too large Load Diff
+21 -16
View File
@@ -1,13 +1,14 @@
{ {
"name": "k8s-set-context-action", "name": "k8s-set-context-action",
"version": "4.0.2", "version": "4.0.0",
"private": true, "private": true,
"main": "lib/index.cjs", "main": "lib/index.js",
"type": "module", "type": "module",
"scripts": { "scripts": {
"build": "tsc --noEmit && node ./scripts/build.mjs", "prebuild": "npm i @vercel/ncc",
"test": "vitest run", "build": "ncc build src/run.ts -o lib",
"test-coverage": "vitest run --coverage", "test": "jest",
"test-coverage": "jest --coverage",
"format": "prettier --write .", "format": "prettier --write .",
"format-check": "prettier --check .", "format-check": "prettier --check .",
"prepare": "husky" "prepare": "husky"
@@ -20,20 +21,24 @@
"author": "GitHub", "author": "GitHub",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@actions/core": "^2.0.2", "@actions/core": "^1.11.1",
"@actions/exec": "^2.0.0", "@actions/exec": "^1.1.1",
"@actions/io": "^2.0.0", "@actions/io": "^1.1.3",
"@kubernetes/client-node": "^1.4.0", "@kubernetes/client-node": "^1.3.0",
"husky": "^9.1.7", "husky": "^9.1.7",
"js-yaml": "^4.1.1" "js-yaml": "^4.1.0"
}, },
"devDependencies": { "devDependencies": {
"@babel/preset-env": "^7.28.0",
"@babel/preset-typescript": "^7.27.1",
"@types/jest": "^30.0.0",
"@types/js-yaml": "^4.0.9", "@types/js-yaml": "^4.0.9",
"@types/node": "^25.0.9", "@types/node": "^24.0.13",
"@vitest/coverage-v8": "^2.0.4", "@vercel/ncc": "^0.38.3",
"esbuild": "^0.24.0", "babel-jest": "^30.0.4",
"prettier": "^3.8.0", "jest": "^30.0.4",
"typescript": "^5.9.3", "prettier": "^3.6.2",
"vitest": "^2.0.4" "ts-jest": "^29.4.0",
"typescript": "^5.8.3"
} }
} }
-26
View File
@@ -1,26 +0,0 @@
#!/usr/bin/env node
import {build} from 'esbuild'
import {mkdir} from 'fs/promises'
import {dirname} from 'path'
const outFile = 'lib/index.cjs'
const main = async () => {
await mkdir(dirname(outFile), {recursive: true})
await build({
entryPoints: ['src/run.ts'],
bundle: true,
platform: 'node',
target: 'node20',
format: 'cjs',
sourcemap: true,
outfile: outFile,
legalComments: 'none'
})
}
main().catch((err) => {
console.error(err)
process.exit(1)
})
+12 -38
View File
@@ -1,56 +1,30 @@
import {getRequiredInputError} from '../tests/util' import {getRequiredInputError} from '../tests/util'
import {afterEach, beforeEach, describe, expect, it, vi} from 'vitest'
import {run} from './action' import {run} from './action'
import * as fs from 'fs' import fs from 'fs'
import * as path from 'path'
import os from 'os'
import * as utils from './utils' import * as utils from './utils'
import * as core from '@actions/core'
describe('Run', () => { describe('Run', () => {
const initialEnv = {...process.env}
const tempDirs: string[] = []
beforeEach(() => {
vi.restoreAllMocks()
process.env = {...initialEnv}
vi.spyOn(core, 'warning').mockImplementation(() => {})
vi.spyOn(core, 'debug').mockImplementation(() => {})
})
afterEach(() => {
tempDirs.forEach((dir) => {
try {
fs.rmSync(dir, {recursive: true, force: true})
} catch {}
})
tempDirs.length = 0
})
it('throws error without cluster type', async () => { it('throws error without cluster type', async () => {
await expect(run()).rejects.toThrow(getRequiredInputError('cluster-type')) await expect(run()).rejects.toThrow(getRequiredInputError('cluster-type'))
}) })
it('writes kubeconfig and sets context', async () => { it('writes kubeconfig and sets context', async () => {
const kubeconfig = 'kubeconfig' const kubeconfig = 'kubeconfig'
const fixedTimestamp = 42
process.env['INPUT_CLUSTER-TYPE'] = 'default' process.env['INPUT_CLUSTER-TYPE'] = 'default'
const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'kubeconfig-test-')) process.env['RUNNER_TEMP'] = '/sample/path'
tempDirs.push(tmpDir)
process.env['RUNNER_TEMP'] = tmpDir
vi.spyOn(Date, 'now').mockReturnValue(fixedTimestamp) jest
vi.spyOn(utils, 'getKubeconfig').mockResolvedValue(kubeconfig) .spyOn(utils, 'getKubeconfig')
vi.spyOn(utils, 'setContext').mockImplementation(() => kubeconfig) .mockImplementation(async () => kubeconfig)
jest.spyOn(fs, 'writeFileSync').mockImplementation(() => {})
jest.spyOn(fs, 'chmodSync').mockImplementation(() => {})
jest.spyOn(utils, 'setContext').mockImplementation(() => kubeconfig)
await expect(run()).resolves.toBeUndefined() expect(await run())
const outputPath = path.join(tmpDir, `kubeconfig_${fixedTimestamp}`)
expect(fs.existsSync(outputPath)).toBe(true)
expect(fs.readFileSync(outputPath, 'utf-8')).toBe(kubeconfig)
expect(process.env['KUBECONFIG']).toBe(outputPath)
expect(utils.getKubeconfig).toHaveBeenCalled() expect(utils.getKubeconfig).toHaveBeenCalled()
expect(utils.setContext).toHaveBeenCalledWith(kubeconfig) expect(fs.writeFileSync).toHaveBeenCalled()
expect(fs.chmodSync).toHaveBeenCalled()
expect(utils.setContext).toHaveBeenCalled()
}) })
}) })
+5 -13
View File
@@ -1,5 +1,3 @@
import {beforeEach, describe, expect, it, test, vi} from 'vitest'
import * as core from '@actions/core'
import * as actions from '@actions/exec' import * as actions from '@actions/exec'
import * as io from '@actions/io' import * as io from '@actions/io'
import {getRequiredInputError} from '../../tests/util' import {getRequiredInputError} from '../../tests/util'
@@ -7,12 +5,6 @@ import {getArcKubeconfig, KUBECONFIG_LOCATION} from './arc'
import * as az from './azCommands' import * as az from './azCommands'
describe('Arc kubeconfig', () => { describe('Arc kubeconfig', () => {
beforeEach(() => {
vi.restoreAllMocks()
vi.spyOn(core, 'warning').mockImplementation(() => {})
vi.spyOn(core, 'debug').mockImplementation(() => {})
})
test('it throws error without resource group', async () => { test('it throws error without resource group', async () => {
await expect(getArcKubeconfig()).rejects.toThrow( await expect(getArcKubeconfig()).rejects.toThrow(
getRequiredInputError('resource-group') getRequiredInputError('resource-group')
@@ -36,11 +28,11 @@ describe('Arc kubeconfig', () => {
process.env['INPUT_RESOURCE-GROUP'] = group process.env['INPUT_RESOURCE-GROUP'] = group
process.env['INPUT_CLUSTER-NAME'] = name process.env['INPUT_CLUSTER-NAME'] = name
vi.spyOn(io, 'which').mockResolvedValue(path) jest.spyOn(io, 'which').mockImplementation(async () => path)
vi.spyOn(az, 'runAzCliCommand').mockResolvedValue() jest.spyOn(az, 'runAzCliCommand').mockImplementation(async () => {})
vi.spyOn(az, 'runAzKubeconfigCommandBlocking').mockResolvedValue( jest
kubeconfig .spyOn(az, 'runAzKubeconfigCommandBlocking')
) .mockImplementation(async () => kubeconfig)
}) })
it('throws an error without method', async () => { it('throws an error without method', async () => {
+1 -4
View File
@@ -1,15 +1,12 @@
import {describe, expect, test, vi} from 'vitest'
import * as core from '@actions/core'
import * as actions from '@actions/exec' import * as actions from '@actions/exec'
import {runAzCliCommand} from './azCommands' import {runAzCliCommand} from './azCommands'
describe('Az commands', () => { describe('Az commands', () => {
test('it runs an az cli command', async () => { test('it runs an az cli command', async () => {
vi.spyOn(core, 'debug').mockImplementation(() => {})
const path = 'path' const path = 'path'
const args = ['args'] const args = ['args']
vi.spyOn(actions, 'exec').mockResolvedValue(0) jest.spyOn(actions, 'exec').mockImplementation(async () => 0)
expect(await runAzCliCommand(path, args)) expect(await runAzCliCommand(path, args))
expect(actions.exec).toHaveBeenCalledWith(path, args, {}) expect(actions.exec).toHaveBeenCalledWith(path, args, {})
-55
View File
@@ -1,22 +1,8 @@
import {afterEach, beforeEach, describe, expect, test, vi} from 'vitest'
import * as fs from 'fs' import * as fs from 'fs'
import * as core from '@actions/core'
import {getRequiredInputError} from '../../tests/util' import {getRequiredInputError} from '../../tests/util'
import {createKubeconfig, getDefaultKubeconfig} from './default' import {createKubeconfig, getDefaultKubeconfig} from './default'
describe('Default kubeconfig', () => { describe('Default kubeconfig', () => {
const originalEnv = {...process.env}
beforeEach(() => {
vi.restoreAllMocks()
vi.spyOn(core, 'warning').mockImplementation(() => {})
vi.spyOn(core, 'debug').mockImplementation(() => {})
})
afterEach(() => {
process.env = {...originalEnv}
})
test('it creates a kubeconfig with proper format', () => { test('it creates a kubeconfig with proper format', () => {
const certAuth = 'certAuth' const certAuth = 'certAuth'
const token = 'token' const token = 'token'
@@ -76,47 +62,6 @@ describe('Default kubeconfig', () => {
expect(getDefaultKubeconfig()).toBe(kc) expect(getDefaultKubeconfig()).toBe(kc)
}) })
test('returns kubeconfig as plaintext when encoding is plaintext', () => {
const kc = 'example kc'
vi.spyOn(core, 'getInput').mockImplementation((name: string) => {
if (name === 'method') return 'default'
if (name === 'kubeconfig-encoding') return 'plaintext'
if (name === 'kubeconfig') return kc
return ''
})
expect(getDefaultKubeconfig()).toBe(kc)
})
test('it gets default config through base64 kubeconfig input', () => {
const kc = 'example kc'
const base64Kc = Buffer.from(kc, 'utf-8').toString('base64')
vi.spyOn(core, 'getInput').mockImplementation((name: string) => {
if (name === 'method') return 'default'
if (name === 'kubeconfig-encoding') return 'base64'
if (name === 'kubeconfig') return base64Kc
return ''
})
expect(getDefaultKubeconfig()).toBe(kc)
})
test('it throws error for unknown kubeconfig-encoding', () => {
const kc = 'example kc'
const unknownEncoding = 'foobar'
vi.spyOn(core, 'getInput').mockImplementation((name: string) => {
if (name === 'method') return 'default'
if (name === 'kubeconfig-encoding') return unknownEncoding
if (name === 'kubeconfig') return kc
return ''
})
expect(() => getDefaultKubeconfig()).toThrow(
"Invalid kubeconfig-encoding: 'foobar'. Must be 'plaintext' or 'base64'."
)
})
}) })
test('it defaults to default method', () => { test('it defaults to default method', () => {
+1 -21
View File
@@ -44,27 +44,7 @@ export function getDefaultKubeconfig(): string {
} }
default: { default: {
core.debug('Setting context using kubeconfig') core.debug('Setting context using kubeconfig')
enum Encoding { return core.getInput('kubeconfig', {required: true})
Base64 = 'base64',
Plaintext = 'plaintext'
}
const rawKubeconfig = core.getInput('kubeconfig', {required: true})
const encoding =
core.getInput('kubeconfig-encoding')?.toLowerCase() ||
Encoding.Plaintext
if (encoding !== Encoding.Base64 && encoding !== Encoding.Plaintext) {
throw new Error(
`Invalid kubeconfig-encoding: '${encoding}'. Must be 'plaintext' or 'base64'.`
)
}
const kubeconfig =
encoding === Encoding.Base64
? Buffer.from(rawKubeconfig, 'base64').toString('utf-8')
: rawKubeconfig
return kubeconfig
} }
} }
} }
-1
View File
@@ -1,4 +1,3 @@
import {describe, expect, test} from 'vitest'
import {Cluster, parseCluster} from './cluster' import {Cluster, parseCluster} from './cluster'
describe('Cluster type', () => { describe('Cluster type', () => {
-1
View File
@@ -1,4 +1,3 @@
import {describe, expect, test} from 'vitest'
import {parseK8sSecret, K8sSecret} from './k8sSecret' import {parseK8sSecret, K8sSecret} from './k8sSecret'
describe('K8sSecret type', () => { describe('K8sSecret type', () => {
-1
View File
@@ -1,4 +1,3 @@
import {describe, expect, test} from 'vitest'
import {Method, parseMethod} from './method' import {Method, parseMethod} from './method'
describe('Method type', () => { describe('Method type', () => {
+6 -12
View File
@@ -1,31 +1,25 @@
import {beforeEach, describe, expect, test, vi} from 'vitest'
import fs from 'fs' import fs from 'fs'
import * as arc from './kubeconfigs/arc' import * as arc from './kubeconfigs/arc'
import * as def from './kubeconfigs/default' import * as def from './kubeconfigs/default'
import {Cluster} from './types/cluster' import {Cluster} from './types/cluster'
import {getKubeconfig, setContext} from './utils' import {getKubeconfig, setContext} from './utils'
import * as core from '@actions/core'
describe('Utils', () => { describe('Utils', () => {
beforeEach(() => {
vi.restoreAllMocks()
vi.spyOn(core, 'warning').mockImplementation(() => {})
vi.spyOn(core, 'debug').mockImplementation(() => {})
})
describe('get kubeconfig', () => { describe('get kubeconfig', () => {
test('it gets arc kubeconfig when type is arc', async () => { test('it gets arc kubeconfig when type is arc', async () => {
const arcKubeconfig = 'arckubeconfig' const arcKubeconfig = 'arckubeconfig'
vi.spyOn(arc, 'getArcKubeconfig').mockResolvedValue(arcKubeconfig) jest
.spyOn(arc, 'getArcKubeconfig')
.mockImplementation(async () => arcKubeconfig)
expect(await getKubeconfig(Cluster.ARC)).toBe(arcKubeconfig) expect(await getKubeconfig(Cluster.ARC)).toBe(arcKubeconfig)
}) })
test('it defaults to default kubeconfig', async () => { test('it defaults to default kubeconfig', async () => {
const defaultKubeconfig = 'arckubeconfig' const defaultKubeconfig = 'arckubeconfig'
vi.spyOn(def, 'getDefaultKubeconfig').mockImplementation( jest
() => defaultKubeconfig .spyOn(def, 'getDefaultKubeconfig')
) .mockImplementation(() => defaultKubeconfig)
expect(await getKubeconfig(undefined)).toBe(defaultKubeconfig) expect(await getKubeconfig(undefined)).toBe(defaultKubeconfig)
expect(await getKubeconfig(Cluster.GENERIC)).toBe(defaultKubeconfig) expect(await getKubeconfig(Cluster.GENERIC)).toBe(defaultKubeconfig)
+1 -1
View File
@@ -3,5 +3,5 @@
* @param inputName Name of input * @param inputName Name of input
* @returns Error with explanation message * @returns Error with explanation message
*/ */
export const getRequiredInputError = (inputName: string): Error => export const getRequiredInputError = (inputName) =>
Error(`Input required and not supplied: ${inputName}`) Error(`Input required and not supplied: ${inputName}`)
+1 -1
View File
@@ -11,5 +11,5 @@
"@kubernetes/client-node": ["node_modules/@kubernetes/client-node"] "@kubernetes/client-node": ["node_modules/@kubernetes/client-node"]
} }
}, },
"exclude": ["node_modules", "lib"] "exclude": ["node_modules", "tests", "src/**/*.test.ts"]
} }
-20
View File
@@ -1,20 +0,0 @@
import {defineConfig} from 'vitest/config'
export default defineConfig({
test: {
environment: 'node',
include: ['**/*.test.ts'],
restoreMocks: true,
clearMocks: true,
mockReset: true,
coverage: {
provider: 'v8',
thresholds: {
statements: 22,
branches: 0,
functions: 40,
lines: 22
}
}
}
})