Repository: hyperjumptech/monika
Pull request #1272 (2024-04-17 10:36:27)

Refactor: Separate Create Config Handler
# Monika Pull Request (PR)

## What feature/issue does this PR add

To make the transition to multi-command (#1154) easier, this changes extract the create config handler into one file. 

## How did you implement / how did you fix it

Extract create config handler into a file.

## How to test

- Run `npm run start -- --create-config`
- Run `npm test`


Patch:
diff --git a/README.md b/README.md
index aa8cd2ae..e7cc5b0f 100644
--- a/README.md
+++ b/README.md
@@ -79,20 +79,20 @@ probes:
         followRedirects: 0
     alerts:
       - assertion: response.status != 302
-        message: You should not follow redirect
+        message: You should not follow the redirect
   - id: 'should follow redirect with default config'
     requests:
       - url: http://localhost:3000/absolute-redirect/20
     alerts:
       - assertion: response.status == 302
-        message: You are not follow redirect
-  - id: 'should follow redirect with customize config'
+        message: You are not following the redirect
+  - id: 'should follow redirect with customized config'
     requests:
       - url: http://localhost:3000/status/302
         followRedirects: 2
     alerts:
       - assertion: response.status == 302
-        message: You are not follow redirect
+        message: You are not following the redirect
 ```
 
 ##### MariaDB
diff --git a/src/commands/monika.ts b/src/commands/monika.ts
index cb82e426..a653a2a0 100644
--- a/src/commands/monika.ts
+++ b/src/commands/monika.ts
@@ -28,7 +28,8 @@ import pEvent from 'p-event'
 import type { Config } from '../interfaces/config'
 import type { Probe } from '../interfaces/probe'
 
-import { createConfig, getConfig, isSymonModeFrom } from '../components/config'
+import { getConfig, isSymonModeFrom } from '../components/config'
+import { createConfig } from '../components/config/create'
 import { sortProbes } from '../components/config/sort'
 import { printAllLogs } from '../components/logger'
 import { flush } from '../components/logger/flush'
@@ -350,7 +351,7 @@ export default class Monika extends Command {
 
     try {
       if (_flags['create-config']) {
-        await createConfig(_flags)
+        await createConfig()
         return
       }
 
diff --git a/src/components/config/create.ts b/src/components/config/create.ts
new file mode 100644
index 00000000..5c7fb2f5
--- /dev/null
+++ b/src/components/config/create.ts
@@ -0,0 +1,110 @@
+import { spawnSync } from 'node:child_process'
+import { existsSync } from 'node:fs'
+import { writeFile } from 'node:fs/promises'
+import { type } from 'node:os'
+
+import { ux } from '@oclif/core'
+import yml from 'js-yaml'
+
+import { addDefaultNotifications } from './get'
+import { parseConfig } from './parse'
+import { getContext } from '../../context'
+import { log } from '../../utils/pino'
+
+export async function createConfig(): Promise<void> {
+  const { flags } = getContext()
+
+  const isConvertFromOtherFormat =
+    flags.har || flags.insomnia || flags.postman || flags.sitemap || flags.text
+  if (!isConvertFromOtherFormat) {
+    log.info(
+      'Opening Monika Configuration Generator in your default browser...'
+    )
+    open('https://hyperjumptech.github.io/monika-config-generator/')
+    return
+  }
+
+  const { path, type } = getPathAndType()
+
+  if (!existsSync(path)) {
+    throw new Error(`Couldn't found the ${path} file.`)
+  }
+
+  const parse = await parseConfig(path, type, flags)
+  const file = flags.output || 'monika.yml'
+
+  if (existsSync(file) && !flags.force) {
+    const answer = await ux.ux.prompt(
+      `\n${file} file is already exists. Overwrite (Y/n)?`
+    )
+
+    if (answer.toLowerCase() !== 'y') {
+      log.warn(
+        'Command cancelled. You can use the -o flag to specify an output file or --force to overwrite without prompting.'
+      )
+      return
+    }
+  }
+
+  const data = yml.dump(addDefaultNotifications(parse))
+  await writeFile(file, data, {
+    encoding: 'utf8',
+  })
+  log.info(`${file} file has been created.`)
+}
+
+function open(url: string) {
+  const operatingSystem = type()
+
+  switch (operatingSystem) {
+    case 'Darwin': {
+      spawnSync('open', [url])
+      break
+    }
+
+    case 'Linux': {
+      spawnSync('xdg-open', [url])
+      break
+    }
+
+    case 'Windows NT': {
+      spawnSync('start', [url])
+      break
+    }
+
+    default: {
+      throw new Error(`Unknown operating system: ${operatingSystem}`)
+    }
+  }
+}
+
+type PathAndType = {
+  path: string
+  type: string
+}
+
+function getPathAndType(): PathAndType {
+  const { flags } = getContext()
+
+  if (flags.har) {
+    return { path: flags.har, type: 'har' }
+  }
+
+  if (flags.insomnia) {
+    return { path: flags.insomnia, type: 'insomnia' }
+  }
+
+  if (flags.postman) {
+    return { path: flags.postman, type: 'postman' }
+  }
+
+  if (flags.sitemap) {
+    return { path: flags.sitemap, type: 'sitemap' }
+  }
+
+  if (flags.text) {
+    return { path: flags.text, type: 'text' }
+  }
+
+  throw new Error('Unknown format')
+}
diff --git a/src/components/config/index.ts b/src/components/config/index.ts
index 5808b991..7d9a8c7a 100644
--- a/src/components/config/index.ts
+++ b/src/components/config/index.ts
@@ -23,8 +23,6 @@
  **********************************************************************************/
 
 import { watch } from 'chokidar'
-import { ux } from '@oclif/core'
-import { existsSync, writeFileSync } from 'fs'
 import isUrl from 'is-url'
 
 import events from '../../events'
@@ -34,19 +32,12 @@ import { monikaFlagsDefaultValue } from '../../flag'
 import type { MonikaFlags } from '../../flag'
 import { getEventEmitter } from '../../utils/events'
 import { md5Hash } from '../../utils/hash'
-import { open } from '../../utils/open-website'
 import { log } from '../../utils/pino'
 import { parseConfig } from './parse'
 import { validateConfig } from './validate'
 import { createConfigFile } from './create-config'
-import yml from 'js-yaml'
 import { exit } from 'process'
-import {
-  type ConfigType,
-  addDefaultNotifications,
-  getConfigFrom,
-  mergeConfigs,
-} from './get'
+import { type ConfigType, getConfigFrom, mergeConfigs } from './get'
 import { getProbes, setProbes } from './probe'
 import { getErrorMessage } from '../../utils/catch-error-handler'
 
@@ -231,84 +222,6 @@ function watchConfigFile({ flags, path }: WatchConfigFileParams) {
   }
 }
 
-const getPathAndTypeFromFlag = (flags: MonikaFlags) => {
-  let path = flags.config?.[0]
-  let type = 'monika'
-
-  if (flags.postman) {
-    path = flags.postman
-    type = 'postman'
-  }
-
-  if (flags.har) {
-    path = flags.har
-    type = 'har'
-  }
-
-  if (flags.insomnia) {
-    path = flags.insomnia
-    type = 'insomnia'
-  }
-
-  if (flags.sitemap) {
-    path = flags.sitemap
-    type = 'sitemap'
-  }
-
-  if (flags.text) {
-    path = flags.text
-    type = 'text'
-  }
-
-  return {
-    path,
-    type,
-  }
-}
-
-export const createConfig = async (flags: MonikaFlags): Promise<void> => {
-  if (
-    !flags.har &&
-    !flags.postman &&
-    !flags.insomnia &&
-    !flags.sitemap &&
-    !flags.text
-  ) {
-    log.info(
-      'Opening Monika Configuration Generator in your default browser...'
-    )
-    open('https://hyperjumptech.github.io/monika-config-generator/')
-  } else {
-    const { path, type } = getPathAndTypeFromFlag(flags)
-
-    if (!existsSync(path)) {
-      log.error(`Couldn't found the ${path} file.`)
-      return
-    }
-
-    const parse = await parseConfig(path, type, flags)
-    const result = addDefaultNotifications(parse)
-    const file = flags.output || 'monika.yml'
-
-    if (existsSync(file) && !flags.force) {
-      const ans = await ux.ux.prompt(
-        `\n${file} file is already exists. Overwrite (Y/n)?`
-      )
-
-      if (ans.toLowerCase() !== 'y') {
-        log.warn(
-          `Command cancelled. You can use the -o flag to specify an output file or --force to overwrite without prompting.`
-        )
-        return
-      }
-    }
-
-    const yamlDoc = yml.dump(result)
-    writeFileSync(file, yamlDoc, 'utf8')
-    log.info(`${file} file has been created.`)
-  }
-}
-
 export function isSymonModeFrom({
   symonKey,
   symonUrl,
diff --git a/src/utils/open-website.ts b/src/utils/open-website.ts
deleted file mode 100644
index 7d6c7610..00000000
--- a/src/utils/open-website.ts
+++ /dev/null
@@ -1,50 +0,0 @@
-/**********************************************************************************
- * MIT License                                                                    *
- *                                                                                *
- * Copyright (c) 2021 Hyperjump Technology                                        *
- *                                                                                *
- * Permission is hereby granted, free of charge, to any person obtaining a copy   *
- * of this software and associated documentation files (the "Software"), to deal  *
- * in the Software without restriction, including without limitation the rights   *
- * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell      *
- * copies of the Software, and to permit persons to whom the Software is          *
- * furnished to do so, subject to the following conditions:                       *
- *                                                                                *
- * The above copyright notice and this permission notice shall be included in all *
- * copies or substantial portions of the Software.                                *
- *                                                                                *
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR     *
- * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,       *
- * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE    *
- * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER         *
- * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,  *
- * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE  *
- * SOFTWARE.                                                                      *
- **********************************************************************************/
-
-import { spawnSync } from 'child_process'
-import { type } from 'os'
-
-export const open = (url: string): void => {
-  const operatingSystem = type()
-  switch (operatingSystem) {
-    case 'Darwin': {
-      spawnSync('open', [url])
-      break
-    }
-
-    case 'Linux': {
-      spawnSync('xdg-open', [url])
-      break
-    }
-
-    case 'Windows NT': {
-      spawnSync('start', [url])
-      break
-    }
-
-    default: {
-      throw new Error(`Unknown operating system: ${operatingSystem}`)
-    }
-  }
-}


Test patch:
diff --git a/src/components/config/__tests__/create-config.test.ts b/src/components/config/__tests__/create-config.test.ts
deleted file mode 100644
index 3049e695..00000000
--- a/src/components/config/__tests__/create-config.test.ts
+++ /dev/null
@@ -1,240 +0,0 @@
-/**********************************************************************************
- * MIT License                                                                    *
- *                                                                                *
- * Copyright (c) 2021 Hyperjump Technology                                        *
- *                                                                                *
- * Permission is hereby granted, free of charge, to any person obtaining a copy   *
- * of this software and associated documentation files (the "Software"), to deal  *
- * in the Software without restriction, including without limitation the rights   *
- * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell      *
- * copies of the Software, and to permit persons to whom the Software is          *
- * furnished to do so, subject to the following conditions:                       *
- *                                                                                *
- * The above copyright notice and this permission notice shall be included in all *
- * copies or substantial portions of the Software.                                *
- *                                                                                *
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR     *
- * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,       *
- * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE    *
- * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER         *
- * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,  *
- * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE  *
- * SOFTWARE.                                                                      *
- **********************************************************************************/
-
-import { expect } from 'chai'
-import fs from 'fs'
-import _ from 'lodash'
-
-import type { MonikaFlags } from '../../../flag'
-import { createConfig } from '../'
-
-beforeEach(() => {
-  if (fs.existsSync('monika.har.yml')) {
-    fs.unlinkSync('monika.har.yml')
-  }
-
-  if (fs.existsSync('monika.postman-basic.yml')) {
-    fs.unlinkSync('monika.postman-basic.yml')
-  }
-
-  if (fs.existsSync('monika.postman-grouped.yml')) {
-    fs.unlinkSync('monika.postman-grouped.yml')
-  }
-
-  if (fs.existsSync('sitemap.xml')) {
-    fs.unlinkSync('sitemap.xml')
-  }
-
-  if (fs.existsSync('monika.insomnia.yml')) {
-    fs.unlinkSync('monika.insomnia.yml')
-  }
-
-  if (fs.existsSync('monika.sitemap.yml')) {
-    fs.unlinkSync('monika.sitemap.yml')
-  }
-})
-
-describe('Har config', () => {
-  describe('Create config from har file', () => {
-    it('should create config from har file', async () => {
-      const flags = {
-        har: './src/components/config/__tests__/form_encoded.har',
-        output: 'monika.har.yml',
-      } as MonikaFlags
-      await createConfig(flags)
-      expect(fs.lstatSync('monika.har.yml').isFile()).to.be.true
-
-      const generated = fs.readFileSync('monika.har.yml', 'utf8')
-      const expected = fs.readFileSync(
-        './src/components/config/__tests__/expected.har.yml',
-        'utf8'
-      )
-      expect(_.isEqual(generated, expected)).to.be.true
-    })
-  })
-})
-
-describe('Text config', () => {
-  describe('Create config from text file', () => {
-    it('should create config from text file', async () => {
-      const flags = {
-        text: './src/components/config/__tests__/textfile',
-        output: 'monika.textfile.yml',
-        force: true,
-      } as MonikaFlags
-      await createConfig(flags)
-      expect(fs.lstatSync('monika.textfile.yml').isFile()).to.be.true
-
-      const generated = fs.readFileSync('monika.textfile.yml', 'utf8')
-      const expected = fs.readFileSync(
-        './src/components/config/__tests__/expected.textfile.yml',
-        'utf8'
-      )
-      expect(_.isEqual(generated, expected)).to.be.true
-    })
-  })
-})
-
-describe('Sitemap config', () => {
-  describe('Create config from xml file', () => {
-    it('should create config from xml file', async () => {
-      const flags = {
-        sitemap: './src/components/config/__tests__/sitemap.xml',
-        output: 'monika.sitemap.yml',
-      } as MonikaFlags
-      await createConfig(flags)
-      expect(fs.lstatSync('monika.sitemap.yml').isFile()).to.be.true
-
-      const generated = fs.readFileSync('monika.sitemap.yml', 'utf8')
-      const expected = fs.readFileSync(
-        './src/components/config/__tests__/expected.sitemap.yml',
-        'utf8'
-      )
-      expect(_.isEqual(generated, expected)).to.be.true
-    })
-  })
-})
-
-describe('Sitemap config [--one-probe ]', () => {
-  describe('Create config from xml file', () => {
-    it('should create config from xml file', async () => {
-      const flags = {
-        sitemap: './src/components/config/__tests__/sitemap.xml',
-        output: 'monika.sitemap.yml',
-        'one-probe': true,
-      } as MonikaFlags
-      await createConfig(flags)
-      expect(fs.lstatSync('monika.sitemap.yml').isFile()).to.be.true
-
-      const generated = fs.readFileSync('monika.sitemap.yml', 'utf8')
-      const expected = fs.readFileSync(
-        './src/components/config/__tests__/expected.sitemap-oneprobe.yml',
-        'utf8'
-      )
-      expect(_.isEqual(generated, expected)).to.be.true
-    })
-  })
-})
-
-const getPostmanConfig = ({ grouped }: { grouped: boolean }) => {
-  if (grouped) {
-    const generated = fs.readFileSync('monika.postman-grouped.yml', 'utf8')
-
-    const expected = fs.readFileSync(
-      './src/components/config/__tests__/mock_files/expected.postman-grouped.yml',
-      'utf8'
-    )
-
-    return { generated, expected }
-  }
-
-  const generated = fs.readFileSync('monika.postman-basic.yml', 'utf8')
-
-  const expected = fs.readFileSync(
-    './src/components/config/__tests__/mock_files/expected.postman-basic.yml',
-    'utf8'
-  )
-
-  return { generated, expected }
-}
-
-describe('Postman config', () => {
-  describe('Create config from postman file', () => {
-    it('[v2.0] - should create config from basic postman file', async () => {
-      const flags = {
-        postman:
-          './src/components/config/__tests__/mock_files/basic-postman_collection-v2.0.json',
-        output: 'monika.postman-basic.yml',
-      } as MonikaFlags
-
-      await createConfig(flags)
-      expect(fs.lstatSync('monika.postman-basic.yml').isFile()).to.be.true
-
-      const { generated, expected } = getPostmanConfig({ grouped: false })
-      expect(_.isEqual(generated, expected)).to.be.true
-    })
-
-    it('[v2.1] - should create config from basic postman file', async () => {
-      const flags = {
-        postman:
-          './src/components/config/__tests__/mock_files/basic-postman_collection-v2.1.json',
-        output: 'monika.postman-basic.yml',
-      } as MonikaFlags
-
-      await createConfig(flags)
-      expect(fs.lstatSync('monika.postman-basic.yml').isFile()).to.be.true
-
-      const { generated, expected } = getPostmanConfig({ grouped: false })
-      expect(_.isEqual(generated, expected)).to.be.true
-    })
-
-    it('[v2.0] - should create config from grouped postman file', async () => {
-      const flags = {
-        postman:
-          './src/components/config/__tests__/mock_files/grouped-postman_collection-v2.0.json',
-        output: 'monika.postman-grouped.yml',
-      } as MonikaFlags
-
-      await createConfig(flags)
-      expect(fs.lstatSync('monika.postman-grouped.yml').isFile()).to.be.true
-
-      const { generated, expected } = getPostmanConfig({ grouped: true })
-      expect(_.isEqual(generated, expected)).to.be.true
-    })
-
-    it('[v2.1] - should create config from grouped postman file', async () => {
-      const flags = {
-        postman:
-          './src/components/config/__tests__/mock_files/grouped-postman_collection-v2.1.json',
-        output: 'monika.postman-grouped.yml',
-      } as MonikaFlags
-
-      await createConfig(flags)
-      expect(fs.lstatSync('monika.postman-grouped.yml').isFile()).to.be.true
-
-      const { generated, expected } = getPostmanConfig({ grouped: true })
-      expect(_.isEqual(generated, expected)).to.be.true
-    })
-  })
-})
-
-describe('Insomnia config', () => {
-  describe('Create config from insomnia file', () => {
-    it('should create config from insomnia file', async () => {
-      const flags = {
-        insomnia: './src/components/config/__tests__/petstore.insomnia.yaml',
-        output: 'monika.insomnia.yml',
-      } as MonikaFlags
-      await createConfig(flags)
-      expect(fs.lstatSync('monika.insomnia.yml').isFile()).to.be.true
-
-      const generated = fs.readFileSync('monika.insomnia.yml', 'utf8')
-      const expected = fs.readFileSync(
-        './src/components/config/__tests__/expected.insomnia.yml',
-        'utf8'
-      )
-      expect(_.isEqual(generated, expected)).to.be.true
-    })
-  })
-})
diff --git a/src/components/config/create.test.ts b/src/components/config/create.test.ts
new file mode 100644
index 00000000..c0da2fb6
--- /dev/null
+++ b/src/components/config/create.test.ts
@@ -0,0 +1,429 @@
+import childProcess from 'node:child_process'
+import { existsSync } from 'node:fs'
+import { rename, readFile, writeFile, rm } from 'node:fs/promises'
+import os from 'node:os'
+
+import { expect } from '@oclif/test'
+import { type SinonStub, stub } from 'sinon'
+
+import { getContext, resetContext, setContext } from '../../context'
+import { getErrorMessage } from '../../utils/catch-error-handler'
+import { createConfig } from './create'
+
+describe('Create config', () => {
+  describe('Open Monika config generator', () => {
+    let osTypeStub: SinonStub
+    let spawnSyncStub: SinonStub
+
+    beforeEach(() => {
+      osTypeStub = stub(os, 'type')
+      spawnSyncStub = stub(childProcess, 'spawnSync')
+    })
+
+    afterEach(() => {
+      osTypeStub.restore()
+      spawnSyncStub.restore()
+    })
+
+    it('should open Monika config generator using MacOS', async () => {
+      // arrange
+      osTypeStub.returns('Darwin')
+
+      // act
+      await createConfig()
+
+      // assert
+      const command = spawnSyncStub.args[0][0]
+      const url = spawnSyncStub.args[0][1][0]
+
+      expect(command).eq('open')
+      expect(url).eq('https://hyperjumptech.github.io/monika-config-generator/')
+    })
+
+    it('should open Monika config generator using Linux', async () => {
+      // arrange
+      osTypeStub.returns('Linux')
+
+      // act
+      await createConfig()
+
+      // assert
+      const command = spawnSyncStub.args[0][0]
+      const url = spawnSyncStub.args[0][1][0]
+
+      expect(command).eq('xdg-open')
+      expect(url).eq('https://hyperjumptech.github.io/monika-config-generator/')
+    })
+
+    it('should open Monika config generator using Windows', async () => {
+      // arrange
+      osTypeStub.returns('Windows NT')
+
+      // act
+      await createConfig()
+
+      // assert
+      const command = spawnSyncStub.args[0][0]
+      const url = spawnSyncStub.args[0][1][0]
+
+      expect(command).eq('start')
+      expect(url).eq('https://hyperjumptech.github.io/monika-config-generator/')
+    })
+
+    it('should throw an error on unhandled OS', async () => {
+      // arrange
+      let errorMessage = ''
+      osTypeStub.returns('FreeBSD')
+
+      // act
+      try {
+        await createConfig()
+      } catch (error) {
+        errorMessage = getErrorMessage(error)
+      }
+
+      // assert
+      expect(errorMessage).to.include('Unknown operating system')
+    })
+  })
+
+  describe('Converter', () => {
+    afterEach(() => {
+      resetContext()
+    })
+
+    it('should throw an error if file path is not found', async () => {
+      // arrange
+      const filepath = './example.har'
+      setContext({ flags: { ...getContext().flags, har: filepath } })
+      let errorMessage = ''
+
+      // act
+      try {
+        await createConfig()
+      } catch (error) {
+        errorMessage = getErrorMessage(error)
+      }
+
+      // assert
+      expect(errorMessage).to.include("Couldn't found")
+    })
+
+    it('should convert from har', async () => {
+      // arrange
+      const output = 'monika.har.yml'
+      setContext({
+        flags: {
+          ...getContext().flags,
+          force: true,
+          output,
+          har: './src/components/config/__tests__/form_encoded.har',
+        },
+      })
+
+      // act
+      await createConfig()
+
+      // assert
+      const generated = await readFile(output, { encoding: 'utf8' })
+      const expected = await readFile(
+        './src/components/config/__tests__/expected.har.yml',
+        { encoding: 'utf8' }
+      )
+      expect(generated).eq(expected)
+
+      await rm(output)
+    })
+
+    it('should convert from insomnia', async () => {
+      // arrange
+      const output = 'monika.insomnia.yml'
+      setContext({
+        flags: {
+          ...getContext().flags,
+          force: true,
+          insomnia: './src/components/config/__tests__/petstore.insomnia.yaml',
+          output,
+        },
+      })
+
+      // act
+      await createConfig()
+
+      // assert
+      const generated = await readFile(output, {
+        encoding: 'utf8',
+      })
+      const expected = await readFile(
+        './src/components/config/__tests__/expected.insomnia.yml',
+        { encoding: 'utf8' }
+      )
+      expect(generated).eq(expected)
+
+      await rm(output)
+    })
+
+    it('should convert from basic postman file v2.0', async () => {
+      // arrange
+      const output = 'monika.postman-basic.yml'
+      setContext({
+        flags: {
+          ...getContext().flags,
+          force: true,
+          postman:
+            './src/components/config/__tests__/mock_files/basic-postman_collection-v2.0.json',
+          output,
+        },
+      })
+
+      // act
+      await createConfig()
+
+      // assert
+      const { generated, expected } = await getPostmanConfig()
+      expect(generated).eq(expected)
+
+      await rm(output)
+    })
+
+    it('should convert from grouped postman file v2.0', async () => {
+      // arrange
+      const output = 'monika.postman-grouped.yml'
+      setContext({
+        flags: {
+          ...getContext().flags,
+          force: true,
+          postman:
+            './src/components/config/__tests__/mock_files/grouped-postman_collection-v2.0.json',
+          output,
+        },
+      })
+
+      // act
+      await createConfig()
+
+      // assert
+      const { generated, expected } = await getPostmanConfig(true)
+      expect(generated).eq(expected)
+
+      await rm(output)
+    })
+
+    it('should convert from basic postman file v2.1', async () => {
+      // arrange
+      const output = 'monika.postman-basic.yml'
+      setContext({
+        flags: {
+          ...getContext().flags,
+          force: true,
+          postman:
+            './src/components/config/__tests__/mock_files/basic-postman_collection-v2.0.json',
+          output: 'monika.postman-basic.yml',
+        },
+      })
+
+      // act
+      await createConfig()
+
+      // assert
+      const { generated, expected } = await getPostmanConfig()
+      expect(generated).eq(expected)
+
+      await rm(output)
+    })
+
+    it('should convert from grouped postman file v2.1', async () => {
+      // arrange
+      const output = 'monika.postman-grouped.yml'
+      setContext({
+        flags: {
+          ...getContext().flags,
+          force: true,
+          postman:
+            './src/components/config/__tests__/mock_files/grouped-postman_collection-v2.1.json',
+          output,
+        },
+      })
+
+      // act
+      await createConfig()
+
+      // assert
+      const { generated, expected } = await getPostmanConfig(true)
+      expect(generated).eq(expected)
+
+      await rm(output)
+    })
+
+    it('should convert from sitemap', async () => {
+      // arrange
+      const output = 'monika.sitemap.yml'
+      setContext({
+        flags: {
+          ...getContext().flags,
+          force: true,
+          sitemap: './src/components/config/__tests__/sitemap.xml',
+          output,
+        },
+      })
+
+      // act
+      await createConfig()
+
+      // assert
+      const generated = await readFile('monika.sitemap.yml', {
+        encoding: 'utf8',
+      })
+      const expected = await readFile(
+        './src/components/config/__tests__/expected.sitemap.yml',
+        { encoding: 'utf8' }
+      )
+      expect(generated).eq(expected)
+
+      await rm(output)
+    })
+
+    it('should convert from sitemap - one probe', async () => {
+      // arrange
+      const output = 'monika.sitemap.yml'
+      setContext({
+        flags: {
+          ...getContext().flags,
+          force: true,
+          sitemap: './src/components/config/__tests__/sitemap.xml',
+          output,
+          'one-probe': true,
+        },
+      })
+
+      // act
+      await createConfig()
+
+      // assert
+      const generated = await readFile('monika.sitemap.yml', {
+        encoding: 'utf8',
+      })
+      const expected = await readFile(
+        './src/components/config/__tests__/expected.sitemap-oneprobe.yml',
+        { encoding: 'utf8' }
+      )
+      expect(generated).eq(expected)
+
+      await rm(output)
+    })
+
+    it('should convert from text', async () => {
+      // arrange
+      const output = 'monika.textfile.yml'
+      setContext({
+        flags: {
+          ...getContext().flags,
+          force: true,
+          text: './src/components/config/__tests__/textfile',
+          output,
+        },
+      })
+
+      // act
+      await createConfig()
+
+      // assert
+      const generated = await readFile('monika.textfile.yml', {
+        encoding: 'utf8',
+      })
+      const expected = await readFile(
+        './src/components/config/__tests__/expected.textfile.yml',
+        { encoding: 'utf8' }
+      )
+      expect(generated).eq(expected)
+
+      await rm(output)
+    })
+  })
+
+  describe('Writer', () => {
+    beforeEach(async () => {
+      if (existsSync('monika.yml')) {
+        await rename('monika.yml', 'monika.backup.yml')
+      }
+    })
+
+    afterEach(async () => {
+      if (existsSync('monika.backup.yml')) {
+        await rename('monika.backup.yml', 'monika.yml')
+      }
+
+      resetContext()
+    })
+
+    it('should not prompt to overwrite existing file', async () => {
+      // arrange
+      setContext({
+        flags: {
+          ...getContext().flags,
+          force: true,
+          text: './src/components/config/__tests__/textfile',
+        },
+      })
+      await writeFile('monika.yml', '', { encoding: 'utf8' })
+
+      // act
+      await createConfig()
+
+      // assert
+      expect(existsSync('monika.yml')).eq(true)
+      await rm('monika.yml')
+    })
+
+    it('should write config to the output flag', async () => {
+      // arrange
+      if (existsSync('monika.yaml')) {
+        await rename('monika.yaml', 'monika.backup.yaml')
+      }
+
+      setContext({
+        flags: {
+          ...getContext().flags,
+          force: true,
+          output: 'monika.yaml',
+          text: './src/components/config/__tests__/textfile',
+        },
+      })
+      await writeFile('monika.yaml', '', { encoding: 'utf8' })
+
+      // act
+      await createConfig()
+
+      // assert
+      expect(existsSync('monika.yaml')).eq(true)
+      await rm('monika.yaml')
+      if (existsSync('monika.backup.yaml')) {
+        await rename('monika.backup.yaml', 'monika.yaml')
+      }
+    })
+  })
+})
+
+async function getPostmanConfig(isGrouped = false) {
+  if (isGrouped) {
+    const generated = await readFile('monika.postman-grouped.yml', {
+      encoding: 'utf8',
+    })
+    const expected = await readFile(
+      './src/components/config/__tests__/mock_files/expected.postman-grouped.yml',
+      { encoding: 'utf8' }
+    )
+
+    return { generated, expected }
+  }
+
+  const generated = await readFile('monika.postman-basic.yml', {
+    encoding: 'utf8',
+  })
+  const expected = await readFile(
+    './src/components/config/__tests__/mock_files/expected.postman-basic.yml',
+    { encoding: 'utf8' }
+  )
+
+  return { generated, expected }
+}