Statocyst Development Part 4
Install nft if missing function & TESTING!
Continuing from our last post we'll implement the installNftIfMissing function:
export const installIfNftMissing = (): Promise<
Result<{ performedInstall: boolean }>
> =>
rpipeAsync(
isNftInstalled,
(result) =>
!result
? installNft().then((result) =>
isSuccess(result) ? createSuccess({ performedInstall: true }) : result
)
: createSuccess({ performedInstall: false }),
)
We basically use rpipe to send the result of isNftInstalled to a function where it will use it to decide whether or not to install nft or not.
At this point it would be good to write some tests in nftables/command.test.ts to make sure our handful of functions work. For now we'll run them against our actual command line, but once we verify the response is as we expect, we'll mock the command response so it will run tests when we push it through continuous integration, but it won't break if the CI environment isn't the same as ours.
First lets uninstall nftables so we can start with it uninstalled:
sudo apt remove nftables -y
Then we write a couple tests using Deno's standard test libraries.
/* nftables/command.test.ts */
import { describe, it } from '@std/testing/bdd'
import { expect } from '@std/expect'
import { installIfNftMissing, isNftInstalled } from './command.ts'
import { isSuccess } from '@joyautomation/dark-matter'
describe('NFT Tables Command', () => {
describe('is nft installed', () => {
it('should return true if nft is installed', async () => {
const result = await isNftInstalled()
expect(isSuccess(result)).toBe(true)
if (isSuccess(result)) {
expect(result.output).toBe(true)
}
})
})
it('should install nftables if not installed.', async () => {
const result = await installIfNftMissing()
expect(isSuccess(result)).toBe(true)
if (isSuccess(result)) {
expect(result.output.performedInstall).toBe(false)
}
})
})
Before we run them, we can see here that there is a repeating pattern that we can refactor away, so we'll build a function isResultSuccessfulAndMatches:
const resultIsSuccessAndMatches = <T>(
result: Result<T>,
expected: unknown,
beforeCheck: (result: ResultSuccess<T>) => unknown = (result) =>
result.output,
) => {
expect(isSuccess(result)).toBe(true)
if (isSuccess(result)) {
expect(beforeCheck(result)).toBe(expected)
}
}
This allows us to rewrite our tests more concisely:
import { describe, it } from '@std/testing/bdd'
import { expect } from '@std/expect'
import { installIfNftMissing, isNftInstalled } from './command.ts'
import { isSuccess, Result, ResultSuccess } from '@joyautomation/dark-matter'
const resultIsSuccessAndMatches = <T>(
result: Result<T>,
expected: unknown,
beforeCheck: (result: ResultSuccess<T>) => unknown = (result) =>
result.output,
) => {
expect(isSuccess(result)).toBe(true)
if (isSuccess(result)) {
expect(beforeCheck(result)).toBe(expected)
}
}
describe('NFT Tables Command', () => {
describe('is nft installed', () => {
it('should return true if nft is installed', async () => {
resultIsSuccessAndMatches(await isNftInstalled(), false)
})
})
it('should install nftables if not installed.', async () => {
resultIsSuccessAndMatches(
await installIfNftMissing(),
true,
(result) => result.output.performedInstall,
)
})
})
Now we run our tests:
deno test -A
Excellent, it's working exactly as we'd hoped. We'll come back and mock our command line later since we may want to test our functions against the real command line until we are solid with our APIs.
Next we'll get our types set up, see part 5!