Skip to content

Commit

Permalink
feat(config): ConfigModule
Browse files Browse the repository at this point in the history
Signed-off-by: Lexus Drumgold <[email protected]>
  • Loading branch information
unicornware committed Nov 9, 2023
1 parent 98710bf commit e40a307
Show file tree
Hide file tree
Showing 21 changed files with 501 additions and 14 deletions.
7 changes: 6 additions & 1 deletion .codecov.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,10 @@ component_management:
- type: project
target: 100%
individual_components:
- component_id: config
name: config
paths:
- src/subdomains/config/**/*.ts
- component_id: labels
name: labels
paths:
Expand Down Expand Up @@ -93,4 +97,5 @@ ignore:
- src/main.ts

profiling:
critical_files_paths: []
critical_files_paths:
- src/subdomains/config/config.module.ts
1 change: 1 addition & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,7 @@ Follow the steps below to setup your local development environment:
| `INPUT_API` |
| `INPUT_CONFIG` |
| `INPUT_TOKEN` |
| `INPUT_WORKSPACE` |
| `NODE_NO_WARNINGS` |
| `ZSH_DOTENV_FILE` |

Expand Down
13 changes: 13 additions & 0 deletions __fixtures__/input-config.fixture.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
/**
* @file Fixtures - INPUT_CONFIG
* @module fixtures/input-config
*/

/**
* Location of infrastructure configuration file relative to workspace root.
*
* @const {string} INPUT_CONFIG
*/
const INPUT_CONFIG: string = '.github/infrastructure.yml'

export default INPUT_CONFIG
29 changes: 29 additions & 0 deletions __tests__/setup/env.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
/**
* @file Test Setup Files - Environment
* @module tests/setup/env
*/

import INPUT_API from '#fixtures/input-api.fixture'
import INPUT_TOKEN from '#fixtures/input-token.fixture'
import OWNER from '#fixtures/owner.fixture'
import REPO from '#fixtures/repo.fixture'
import pathe from '@flex-development/pathe'
import { join, noop, type EmptyArray, type Fn } from '@flex-development/tutils'

/**
* Stub environment variables.
*
* @param {Fn<EmptyArray, void>?} [fn=noop] - Stub callback to execute
* @return {void} Nothing when complete
*/
const env = (fn: Fn<EmptyArray, void> = noop): void => {
vi.stubEnv('GITHUB_REPOSITORY', join([OWNER, REPO], pathe.sep))

vi.stubEnv('INPUT_API', INPUT_API)
vi.stubEnv('INPUT_TOKEN', INPUT_TOKEN)
vi.stubEnv('INPUT_WORKSPACE', process.cwd())

return void fn()
}

export default env
12 changes: 6 additions & 6 deletions __tests__/setup/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,17 @@
* @module tests/setup
*/

import INPUT_API from '#fixtures/input-api.fixture'
import INPUT_TOKEN from '#fixtures/input-token.fixture'
import pkg from '#pkg' assert { type: 'json' }
import './chai'
import './faker'
import './matchers'
import './serializers'

import env from './env'

beforeAll(() => {
vi.stubEnv('GITHUB_REPOSITORY', pkg.name.slice(1))
env()
})

vi.stubEnv('INPUT_API', INPUT_API)
vi.stubEnv('INPUT_TOKEN', INPUT_TOKEN)
beforeEach(() => {
env()
})
4 changes: 4 additions & 0 deletions action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,10 @@ inputs:
default: ${{ github.token }}
description: Personal access token (PAT) used to authenticate GitHub API requests
required: false
workspace:
default: ${{ github.workspace }}
description: Path to current working directory
required: false
runs:
main: dist/main.mjs
using: node20
Expand Down
3 changes: 3 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@
"@faker-js/faker": "8.2.0",
"@flex-development/commitlint-config": "1.0.1",
"@flex-development/decorator-regex": "2.0.0",
"@flex-development/errnode": "2.0.0",
"@flex-development/esm-types": "2.0.0",
"@flex-development/grease": "3.0.0-alpha.9",
"@flex-development/mkbuild": "1.0.0-alpha.23",
Expand Down Expand Up @@ -116,6 +117,7 @@
"growl": "1.10.5",
"husky": "8.0.3",
"is-ci": "3.0.1",
"json5": "2.2.3",
"jsonc-eslint-parser": "2.4.0",
"lint-staged": "15.0.2",
"msw": "2.0.3",
Expand All @@ -130,6 +132,7 @@
"vite": "5.0.0-beta.16",
"vite-tsconfig-paths": "4.2.1",
"vitest": "1.0.0-beta.3",
"yaml": "2.3.4",
"yaml-eslint-parser": "1.2.2"
},
"resolutions": {
Expand Down
85 changes: 85 additions & 0 deletions src/subdomains/config/__tests__/config.module.functional.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
/**
* @file Functional Tests - ConfigModule
* @module repostructure/config/tests/functional/ConfigModule
*/

import * as mlly from '@flex-development/mlly'
import pathe from '@flex-development/pathe'
import type { Dot, EmptyString, Join } from '@flex-development/tutils'
import json5 from 'json5'
import * as yaml from 'yaml'
import TestSubject from '../config.module'

vi.mock('@flex-development/mlly', () => ({ getSource: vi.fn() }))
vi.mock('yaml', async og => ({ parse: vi.fn((await og<typeof yaml>()).parse) }))

describe('functional:config/ConfigModule', () => {
describe('.infrastructure', () => {
let workspace: string

beforeAll(() => {
workspace = import.meta.env.INPUT_WORKSPACE
})

describe.each<Join<[Dot, 'json' | 'json5' | 'jsonc'], EmptyString>>([
'.json',
'.json5',
'.jsonc'
])('%s', ext => {
let file: string
let source: string

beforeAll(() => {
file = pathe.join('.github', 'infrastructure' + ext)
source = '{}'
})

beforeEach(() => {
vi.spyOn(mlly, 'getSource').mockImplementationOnce(async () => source)
vi.spyOn(json5, 'parse')
})

it(`should parse file with ${ext} extension`, async () => {
// Act
await TestSubject.infrastructure(file, workspace)

// Expect
expect(vi.mocked(json5.parse)).toHaveBeenCalledOnce()
expect(vi.mocked(json5.parse)).toHaveBeenCalledWith(source)
})
})

describe.each<Join<[Dot, 'yaml' | 'yml'], EmptyString>>([
'.yaml',
'.yml'
])('%s', ext => {
let file: string
let source: string

beforeAll(() => {
file = pathe.join('.github', 'infrastructure' + ext)
source = ''
})

beforeEach(() => {
vi.spyOn(mlly, 'getSource').mockImplementationOnce(async () => source)
vi.spyOn(yaml, 'parse')
})

it(`should parse file with ${ext} extension`, async () => {
// Act
await TestSubject.infrastructure(file, workspace)

// Expect
expect(vi.mocked(yaml.parse)).toHaveBeenCalledOnce()
expect(vi.mocked(yaml.parse)).toHaveBeenCalledWith(source, {
logLevel: 'error',
prettyErrors: true,
schema: 'core',
sortMapEntries: true,
version: 'next'
})
})
})
})
})
33 changes: 33 additions & 0 deletions src/subdomains/config/__tests__/config.module.integration.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
/**
* @file Unit Tests - ConfigModule
* @module repostructure/config/tests/unit/ConfigModule
*/

import INPUT_CONFIG from '#fixtures/input-config.fixture'
import env from '#tests/setup/env'
import { ConfigService } from '@nestjs/config'
import { Test, TestingModuleBuilder } from '@nestjs/testing'
import TestSubject from '../config.module'

describe('unit:config/ConfigModule', () => {
beforeEach(() => {
env((): void => {
return void vi.stubEnv('INPUT_CONFIG', INPUT_CONFIG)
})
})

describe('.forRoot', () => {
it('should export ConfigService', async () => {
// Arrange
const ref: TestingModuleBuilder = Test.createTestingModule({
imports: [TestSubject.forRoot()]
})

// Act
const result = (await ref.compile()).get(ConfigService)

// Expect
expect(result).to.be.instanceof(ConfigService)
})
})
})
70 changes: 70 additions & 0 deletions src/subdomains/config/__tests__/config.module.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
/**
* @file Unit Tests - ConfigModule
* @module repostructure/config/tests/unit/ConfigModule
*/

import CLIENT_MUTATION_ID from '#fixtures/client-mutation-id.fixture'
import INPUT_CONFIG from '#fixtures/input-config.fixture'
import OWNER from '#fixtures/owner.fixture'
import REPO from '#fixtures/repo.fixture'
import env from '#tests/setup/env'
import type { NodeError } from '@flex-development/errnode'
import pathe from '@flex-development/pathe'
import { fileURLToPath } from 'node:url'
import TestSubject from '../config.module'

describe('unit:config/ConfigModule', () => {
beforeEach(() => {
env((): void => {
return void vi.stubEnv('INPUT_CONFIG', INPUT_CONFIG)
})
})

describe('.forRoot', () => {
it('should return dynamic global module', () => {
// Act
const result = TestSubject.forRoot()

// Expect
expect(result).to.have.property('global').be.true
expect(result).to.have.property('module', TestSubject)
})
})

describe('.infrastructure', () => {
it('should throw if file extension is invalid', async () => {
// Arrange
const workspace: string = import.meta.env.INPUT_WORKSPACE
const path: string = fileURLToPath(import.meta.url)
const file: string = pathe.relative(workspace, path)
let error!: NodeError

// Act
try {
await TestSubject.infrastructure(file, workspace)
} catch (e: unknown) {
error = <typeof error>e
}

// Expect
expect(error).to.have.property('code', 'ERR_UNKNOWN_FILE_EXTENSION')
})
})

describe('.load', () => {
it('should return configuration object', async () => {
// Act
const result = await TestSubject.load()

// Expect
expect(result).toMatchObject({
api: import.meta.env.INPUT_API,
id: CLIENT_MUTATION_ID,
infrastructure: expect.any(Object),
owner: OWNER,
repo: REPO,
token: import.meta.env.INPUT_TOKEN
})
})
})
})
Loading

0 comments on commit e40a307

Please sign in to comment.