Merge branch 'next/minor' of github.com:Start9Labs/start-os into next/major

This commit is contained in:
Matt Hill
2024-11-25 19:02:07 -07:00
712 changed files with 83068 additions and 9240 deletions

View File

@@ -1,839 +0,0 @@
import { testOutput } from "./output.test"
import { Config } from "../config/builder/config"
import { List } from "../config/builder/list"
import { Value } from "../config/builder/value"
import { Variants } from "../config/builder/variants"
import { ValueSpec } from "../config/configTypes"
import { setupManifest } from "../manifest/setupManifest"
import { StartSdk } from "../StartSdk"
import { VersionGraph } from "../version/VersionGraph"
import { VersionInfo } from "../version/VersionInfo"
describe("builder tests", () => {
test("text", async () => {
const bitcoinPropertiesBuilt: {
"peer-tor-address": ValueSpec
} = await Config.of({
"peer-tor-address": Value.text({
name: "Peer tor address",
description: "The Tor address of the peer interface",
required: { default: null },
}),
}).build({} as any)
expect(bitcoinPropertiesBuilt).toMatchObject({
"peer-tor-address": {
type: "text",
description: "The Tor address of the peer interface",
warning: null,
masked: false,
placeholder: null,
minLength: null,
maxLength: null,
patterns: [],
disabled: false,
inputmode: "text",
name: "Peer tor address",
required: true,
default: null,
},
})
})
})
describe("values", () => {
test("toggle", async () => {
const value = Value.toggle({
name: "Testing",
description: null,
warning: null,
default: false,
})
const validator = value.validator
validator.unsafeCast(false)
testOutput<typeof validator._TYPE, boolean>()(null)
})
test("text", async () => {
const value = Value.text({
name: "Testing",
required: { default: null },
})
const validator = value.validator
const rawIs = await value.build({} as any)
validator.unsafeCast("test text")
expect(() => validator.unsafeCast(null)).toThrowError()
testOutput<typeof validator._TYPE, string>()(null)
})
test("text with default", async () => {
const value = Value.text({
name: "Testing",
required: { default: "this is a default value" },
})
const validator = value.validator
const rawIs = await value.build({} as any)
validator.unsafeCast("test text")
expect(() => validator.unsafeCast(null)).toThrowError()
testOutput<typeof validator._TYPE, string>()(null)
})
test("optional text", async () => {
const value = Value.text({
name: "Testing",
required: false,
})
const validator = value.validator
const rawIs = await value.build({} as any)
validator.unsafeCast("test text")
validator.unsafeCast(null)
testOutput<typeof validator._TYPE, string | null | undefined>()(null)
})
test("color", async () => {
const value = Value.color({
name: "Testing",
required: false,
description: null,
warning: null,
})
const validator = value.validator
validator.unsafeCast("#000000")
testOutput<typeof validator._TYPE, string | null | undefined>()(null)
})
test("datetime", async () => {
const value = Value.datetime({
name: "Testing",
required: { default: null },
description: null,
warning: null,
inputmode: "date",
min: null,
max: null,
})
const validator = value.validator
validator.unsafeCast("2021-01-01")
testOutput<typeof validator._TYPE, string>()(null)
})
test("optional datetime", async () => {
const value = Value.datetime({
name: "Testing",
required: false,
description: null,
warning: null,
inputmode: "date",
min: null,
max: null,
})
const validator = value.validator
validator.unsafeCast("2021-01-01")
testOutput<typeof validator._TYPE, string | null | undefined>()(null)
})
test("textarea", async () => {
const value = Value.textarea({
name: "Testing",
required: false,
description: null,
warning: null,
minLength: null,
maxLength: null,
placeholder: null,
})
const validator = value.validator
validator.unsafeCast("test text")
testOutput<typeof validator._TYPE, string>()(null)
})
test("number", async () => {
const value = Value.number({
name: "Testing",
required: { default: null },
integer: false,
description: null,
warning: null,
min: null,
max: null,
step: null,
units: null,
placeholder: null,
})
const validator = value.validator
validator.unsafeCast(2)
testOutput<typeof validator._TYPE, number>()(null)
})
test("optional number", async () => {
const value = Value.number({
name: "Testing",
required: false,
integer: false,
description: null,
warning: null,
min: null,
max: null,
step: null,
units: null,
placeholder: null,
})
const validator = value.validator
validator.unsafeCast(2)
testOutput<typeof validator._TYPE, number | null | undefined>()(null)
})
test("select", async () => {
const value = Value.select({
name: "Testing",
required: { default: null },
values: {
a: "A",
b: "B",
},
description: null,
warning: null,
})
const validator = value.validator
validator.unsafeCast("a")
validator.unsafeCast("b")
expect(() => validator.unsafeCast("c")).toThrowError()
testOutput<typeof validator._TYPE, "a" | "b">()(null)
})
test("nullable select", async () => {
const value = Value.select({
name: "Testing",
required: false,
values: {
a: "A",
b: "B",
},
description: null,
warning: null,
})
const validator = value.validator
validator.unsafeCast("a")
validator.unsafeCast("b")
validator.unsafeCast(null)
testOutput<typeof validator._TYPE, "a" | "b" | null | undefined>()(null)
})
test("multiselect", async () => {
const value = Value.multiselect({
name: "Testing",
values: {
a: "A",
b: "B",
},
default: [],
description: null,
warning: null,
minLength: null,
maxLength: null,
})
const validator = value.validator
validator.unsafeCast([])
validator.unsafeCast(["a", "b"])
expect(() => validator.unsafeCast(["e"])).toThrowError()
expect(() => validator.unsafeCast([4])).toThrowError()
testOutput<typeof validator._TYPE, Array<"a" | "b">>()(null)
})
test("object", async () => {
const value = Value.object(
{
name: "Testing",
description: null,
warning: null,
},
Config.of({
a: Value.toggle({
name: "test",
description: null,
warning: null,
default: false,
}),
}),
)
const validator = value.validator
validator.unsafeCast({ a: true })
testOutput<typeof validator._TYPE, { a: boolean }>()(null)
})
test("union", async () => {
const value = Value.union(
{
name: "Testing",
required: { default: null },
description: null,
warning: null,
},
Variants.of({
a: {
name: "a",
spec: Config.of({
b: Value.toggle({
name: "b",
description: null,
warning: null,
default: false,
}),
}),
},
}),
)
const validator = value.validator
validator.unsafeCast({ selection: "a", value: { b: false } })
type Test = typeof validator._TYPE
testOutput<Test, { selection: "a"; value: { b: boolean } }>()(null)
})
describe("dynamic", () => {
const fakeOptions = {
config: "config",
effects: "effects",
utils: "utils",
} as any
test("toggle", async () => {
const value = Value.dynamicToggle(async () => ({
name: "Testing",
description: null,
warning: null,
default: false,
}))
const validator = value.validator
validator.unsafeCast(false)
expect(() => validator.unsafeCast(null)).toThrowError()
testOutput<typeof validator._TYPE, boolean>()(null)
expect(await value.build(fakeOptions)).toMatchObject({
name: "Testing",
description: null,
warning: null,
default: false,
})
})
test("text", async () => {
const value = Value.dynamicText(async () => ({
name: "Testing",
required: { default: null },
}))
const validator = value.validator
const rawIs = await value.build({} as any)
validator.unsafeCast("test text")
validator.unsafeCast(null)
testOutput<typeof validator._TYPE, string | null | undefined>()(null)
expect(await value.build(fakeOptions)).toMatchObject({
name: "Testing",
required: true,
default: null,
})
})
test("text with default", async () => {
const value = Value.dynamicText(async () => ({
name: "Testing",
required: { default: "this is a default value" },
}))
const validator = value.validator
validator.unsafeCast("test text")
validator.unsafeCast(null)
testOutput<typeof validator._TYPE, string | null | undefined>()(null)
expect(await value.build(fakeOptions)).toMatchObject({
name: "Testing",
required: true,
default: "this is a default value",
})
})
test("optional text", async () => {
const value = Value.dynamicText(async () => ({
name: "Testing",
required: false,
}))
const validator = value.validator
const rawIs = await value.build({} as any)
validator.unsafeCast("test text")
validator.unsafeCast(null)
testOutput<typeof validator._TYPE, string | null | undefined>()(null)
expect(await value.build(fakeOptions)).toMatchObject({
name: "Testing",
required: false,
default: null,
})
})
test("color", async () => {
const value = Value.dynamicColor(async () => ({
name: "Testing",
required: false,
description: null,
warning: null,
}))
const validator = value.validator
validator.unsafeCast("#000000")
validator.unsafeCast(null)
testOutput<typeof validator._TYPE, string | null | undefined>()(null)
expect(await value.build(fakeOptions)).toMatchObject({
name: "Testing",
required: false,
default: null,
description: null,
warning: null,
})
})
test("datetime", async () => {
const sdk = StartSdk.of()
.withManifest(
setupManifest(
VersionGraph.of(
VersionInfo.of({
version: "1.0.0:0",
releaseNotes: "",
migrations: {},
}),
),
{
id: "testOutput",
title: "",
license: "",
wrapperRepo: "",
upstreamRepo: "",
supportSite: "",
marketingSite: "",
donationUrl: null,
description: {
short: "",
long: "",
},
containers: {},
images: {},
volumes: [],
assets: [],
alerts: {
install: null,
update: null,
uninstall: null,
restore: null,
start: null,
stop: null,
},
dependencies: {
"remote-test": {
description: "",
optional: true,
s9pk: "https://example.com/remote-test.s9pk",
},
},
},
),
)
.withStore<{ test: "a" }>()
.build(true)
const value = Value.dynamicDatetime<{ test: "a" }>(
async ({ effects }) => {
;async () => {
;(await sdk.store
.getOwn(effects, sdk.StorePath.test)
.once()) satisfies "a"
}
return {
name: "Testing",
required: { default: null },
inputmode: "date",
}
},
)
const validator = value.validator
validator.unsafeCast("2021-01-01")
validator.unsafeCast(null)
testOutput<typeof validator._TYPE, string | null | undefined>()(null)
expect(await value.build(fakeOptions)).toMatchObject({
name: "Testing",
required: true,
default: null,
description: null,
warning: null,
inputmode: "date",
})
})
test("textarea", async () => {
const value = Value.dynamicTextarea(async () => ({
name: "Testing",
required: false,
description: null,
warning: null,
minLength: null,
maxLength: null,
placeholder: null,
}))
const validator = value.validator
validator.unsafeCast("test text")
expect(() => validator.unsafeCast(null)).toThrowError()
testOutput<typeof validator._TYPE, string>()(null)
expect(await value.build(fakeOptions)).toMatchObject({
name: "Testing",
required: false,
})
})
test("number", async () => {
const value = Value.dynamicNumber(() => ({
name: "Testing",
required: { default: null },
integer: false,
description: null,
warning: null,
min: null,
max: null,
step: null,
units: null,
placeholder: null,
}))
const validator = value.validator
validator.unsafeCast(2)
validator.unsafeCast(null)
expect(() => validator.unsafeCast("null")).toThrowError()
testOutput<typeof validator._TYPE, number | null | undefined>()(null)
expect(await value.build(fakeOptions)).toMatchObject({
name: "Testing",
required: true,
})
})
test("select", async () => {
const value = Value.dynamicSelect(() => ({
name: "Testing",
required: { default: null },
values: {
a: "A",
b: "B",
},
description: null,
warning: null,
}))
const validator = value.validator
validator.unsafeCast("a")
validator.unsafeCast("b")
validator.unsafeCast("c")
validator.unsafeCast(null)
testOutput<typeof validator._TYPE, string | null | undefined>()(null)
expect(await value.build(fakeOptions)).toMatchObject({
name: "Testing",
required: true,
})
})
test("multiselect", async () => {
const value = Value.dynamicMultiselect(() => ({
name: "Testing",
values: {
a: "A",
b: "B",
},
default: [],
description: null,
warning: null,
minLength: null,
maxLength: null,
}))
const validator = value.validator
validator.unsafeCast([])
validator.unsafeCast(["a", "b"])
validator.unsafeCast(["c"])
expect(() => validator.unsafeCast([4])).toThrowError()
expect(() => validator.unsafeCast(null)).toThrowError()
testOutput<typeof validator._TYPE, Array<string>>()(null)
expect(await value.build(fakeOptions)).toMatchObject({
name: "Testing",
default: [],
})
})
})
describe("filtering", () => {
test("union", async () => {
const value = Value.filteredUnion(
() => ["a", "c"],
{
name: "Testing",
required: { default: null },
description: null,
warning: null,
},
Variants.of({
a: {
name: "a",
spec: Config.of({
b: Value.toggle({
name: "b",
description: null,
warning: null,
default: false,
}),
}),
},
b: {
name: "b",
spec: Config.of({
b: Value.toggle({
name: "b",
description: null,
warning: null,
default: false,
}),
}),
},
}),
)
const validator = value.validator
validator.unsafeCast({ selection: "a", value: { b: false } })
type Test = typeof validator._TYPE
testOutput<
Test,
| { selection: "a"; value: { b: boolean } }
| { selection: "b"; value: { b: boolean } }
>()(null)
const built = await value.build({} as any)
expect(built).toMatchObject({
name: "Testing",
variants: {
b: {},
},
})
expect(built).toMatchObject({
name: "Testing",
variants: {
a: {},
b: {},
},
})
expect(built).toMatchObject({
name: "Testing",
variants: {
a: {},
b: {},
},
disabled: ["a", "c"],
})
})
})
test("dynamic union", async () => {
const value = Value.dynamicUnion(
() => ({
disabled: ["a", "c"],
name: "Testing",
required: { default: null },
description: null,
warning: null,
}),
Variants.of({
a: {
name: "a",
spec: Config.of({
b: Value.toggle({
name: "b",
description: null,
warning: null,
default: false,
}),
}),
},
b: {
name: "b",
spec: Config.of({
b: Value.toggle({
name: "b",
description: null,
warning: null,
default: false,
}),
}),
},
}),
)
const validator = value.validator
validator.unsafeCast({ selection: "a", value: { b: false } })
type Test = typeof validator._TYPE
testOutput<
Test,
| { selection: "a"; value: { b: boolean } }
| { selection: "b"; value: { b: boolean } }
| null
| undefined
>()(null)
const built = await value.build({} as any)
expect(built).toMatchObject({
name: "Testing",
variants: {
b: {},
},
})
expect(built).toMatchObject({
name: "Testing",
variants: {
a: {},
b: {},
},
})
expect(built).toMatchObject({
name: "Testing",
variants: {
a: {},
b: {},
},
disabled: ["a", "c"],
})
})
})
describe("Builder List", () => {
test("obj", async () => {
const value = Value.list(
List.obj(
{
name: "test",
},
{
spec: Config.of({
test: Value.toggle({
name: "test",
description: null,
warning: null,
default: false,
}),
}),
},
),
)
const validator = value.validator
validator.unsafeCast([{ test: true }])
testOutput<typeof validator._TYPE, { test: boolean }[]>()(null)
})
test("text", async () => {
const value = Value.list(
List.text(
{
name: "test",
},
{
patterns: [],
},
),
)
const validator = value.validator
validator.unsafeCast(["test", "text"])
testOutput<typeof validator._TYPE, string[]>()(null)
})
describe("dynamic", () => {
test("text", async () => {
const value = Value.list(
List.dynamicText(() => ({
name: "test",
spec: { patterns: [] },
})),
)
const validator = value.validator
validator.unsafeCast(["test", "text"])
expect(() => validator.unsafeCast([3, 4])).toThrowError()
expect(() => validator.unsafeCast(null)).toThrowError()
testOutput<typeof validator._TYPE, string[]>()(null)
expect(await value.build({} as any)).toMatchObject({
name: "test",
spec: { patterns: [] },
})
})
})
})
describe("Nested nullable values", () => {
test("Testing text", async () => {
const value = Config.of({
a: Value.text({
name: "Temp Name",
description:
"If no name is provided, the name from config will be used",
required: false,
}),
})
const validator = value.validator
validator.unsafeCast({ a: null })
validator.unsafeCast({ a: "test" })
expect(() => validator.unsafeCast({ a: 4 })).toThrowError()
testOutput<typeof validator._TYPE, { a: string | null | undefined }>()(null)
})
test("Testing number", async () => {
const value = Config.of({
a: Value.number({
name: "Temp Name",
description:
"If no name is provided, the name from config will be used",
required: false,
warning: null,
placeholder: null,
integer: false,
min: null,
max: null,
step: null,
units: null,
}),
})
const validator = value.validator
validator.unsafeCast({ a: null })
validator.unsafeCast({ a: 5 })
expect(() => validator.unsafeCast({ a: "4" })).toThrowError()
testOutput<typeof validator._TYPE, { a: number | null | undefined }>()(null)
})
test("Testing color", async () => {
const value = Config.of({
a: Value.color({
name: "Temp Name",
description:
"If no name is provided, the name from config will be used",
required: false,
warning: null,
}),
})
const validator = value.validator
validator.unsafeCast({ a: null })
validator.unsafeCast({ a: "5" })
expect(() => validator.unsafeCast({ a: 4 })).toThrowError()
testOutput<typeof validator._TYPE, { a: string | null | undefined }>()(null)
})
test("Testing select", async () => {
const value = Config.of({
a: Value.select({
name: "Temp Name",
description:
"If no name is provided, the name from config will be used",
required: false,
warning: null,
values: {
a: "A",
},
}),
})
const higher = await Value.select({
name: "Temp Name",
description: "If no name is provided, the name from config will be used",
required: false,
warning: null,
values: {
a: "A",
},
}).build({} as any)
const validator = value.validator
validator.unsafeCast({ a: null })
validator.unsafeCast({ a: "a" })
expect(() => validator.unsafeCast({ a: "4" })).toThrowError()
testOutput<typeof validator._TYPE, { a: "a" | null | undefined }>()(null)
})
test("Testing multiselect", async () => {
const value = Config.of({
a: Value.multiselect({
name: "Temp Name",
description:
"If no name is provided, the name from config will be used",
warning: null,
default: [],
values: {
a: "A",
},
minLength: null,
maxLength: null,
}),
})
const validator = value.validator
validator.unsafeCast({ a: [] })
validator.unsafeCast({ a: ["a"] })
expect(() => validator.unsafeCast({ a: ["4"] })).toThrowError()
expect(() => validator.unsafeCast({ a: "4" })).toThrowError()
testOutput<typeof validator._TYPE, { a: "a"[] }>()(null)
})
})

View File

@@ -1,26 +0,0 @@
import { ListValueSpecOf, isValueSpecListOf } from "../config/configTypes"
import { Config } from "../config/builder/config"
import { List } from "../config/builder/list"
import { Value } from "../config/builder/value"
describe("Config Types", () => {
test("isValueSpecListOf", async () => {
const options = [List.obj, List.text]
for (const option of options) {
const test = (option as any)(
{} as any,
{ spec: Config.of({}) } as any,
) as any
const someList = await Value.list(test).build({} as any)
if (isValueSpecListOf(someList, "text")) {
someList.spec satisfies ListValueSpecOf<"text">
} else if (isValueSpecListOf(someList, "object")) {
someList.spec satisfies ListValueSpecOf<"object">
} else {
throw new Error(
"Failed to figure out the type: " + JSON.stringify(someList),
)
}
}
})
})

View File

@@ -1,355 +0,0 @@
import { VersionRange, ExtendedVersion } from "../exver"
describe("ExVer", () => {
{
{
const checker = VersionRange.parse("*")
test("VersionRange.parse('*')", () => {
checker.satisfiedBy(ExtendedVersion.parse("1:0"))
checker.satisfiedBy(ExtendedVersion.parse("1.2:0"))
checker.satisfiedBy(ExtendedVersion.parse("1.2.3:0"))
checker.satisfiedBy(ExtendedVersion.parse("1.2.3:4"))
checker.satisfiedBy(ExtendedVersion.parse("1.2.3:4.5"))
checker.satisfiedBy(ExtendedVersion.parse("1.2.3:4.5.6"))
expect(checker.satisfiedBy(ExtendedVersion.parse("1:0"))).toEqual(true)
expect(checker.satisfiedBy(ExtendedVersion.parse("1.2:0"))).toEqual(
true,
)
expect(checker.satisfiedBy(ExtendedVersion.parse("1.2.3:4"))).toEqual(
true,
)
})
test("VersionRange.parse('*') invalid", () => {
expect(() => checker.satisfiedBy(ExtendedVersion.parse("a"))).toThrow()
expect(() => checker.satisfiedBy(ExtendedVersion.parse(""))).toThrow()
expect(() =>
checker.satisfiedBy(ExtendedVersion.parse("1..3")),
).toThrow()
})
}
{
const checker = VersionRange.parse(">1.2.3:4")
test(`VersionRange.parse(">1.2.3:4") valid`, () => {
expect(
checker.satisfiedBy(ExtendedVersion.parse("2-beta.123:0")),
).toEqual(true)
expect(checker.satisfiedBy(ExtendedVersion.parse("2:0"))).toEqual(true)
expect(checker.satisfiedBy(ExtendedVersion.parse("1.2.3:5"))).toEqual(
true,
)
expect(checker.satisfiedBy(ExtendedVersion.parse("1.2.3:4.1"))).toEqual(
true,
)
})
test(`VersionRange.parse(">1.2.3:4") invalid`, () => {
expect(checker.satisfiedBy(ExtendedVersion.parse("1.2.3:4"))).toEqual(
false,
)
expect(checker.satisfiedBy(ExtendedVersion.parse("1.2.3:0"))).toEqual(
false,
)
expect(checker.satisfiedBy(ExtendedVersion.parse("1:0"))).toEqual(false)
})
}
{
const checker = VersionRange.parse("=1.2.3")
test(`VersionRange.parse("=1.2.3") valid`, () => {
expect(checker.satisfiedBy(ExtendedVersion.parse("1.2.3:0"))).toEqual(
true,
)
})
test(`VersionRange.parse("=1.2.3") invalid`, () => {
expect(checker.satisfiedBy(ExtendedVersion.parse("2:0"))).toEqual(false)
expect(checker.satisfiedBy(ExtendedVersion.parse("1.2.3:1"))).toEqual(
false,
)
expect(checker.satisfiedBy(ExtendedVersion.parse("1.2:0"))).toEqual(
false,
)
})
}
{
const checker = VersionRange.parse(">=1.2.3:4")
test(`VersionRange.parse(">=1.2.3:4") valid`, () => {
expect(checker.satisfiedBy(ExtendedVersion.parse("2:0"))).toEqual(true)
expect(checker.satisfiedBy(ExtendedVersion.parse("1.2.3:5"))).toEqual(
true,
)
expect(checker.satisfiedBy(ExtendedVersion.parse("1.2.3:4.1"))).toEqual(
true,
)
expect(checker.satisfiedBy(ExtendedVersion.parse("1.2.3:4"))).toEqual(
true,
)
})
test(`VersionRange.parse(">=1.2.3:4") invalid`, () => {
expect(checker.satisfiedBy(ExtendedVersion.parse("1.2.3:0"))).toEqual(
false,
)
expect(checker.satisfiedBy(ExtendedVersion.parse("1:0"))).toEqual(false)
})
}
{
const checker = VersionRange.parse("<1.2.3:4")
test(`VersionRange.parse("<1.2.3:4") invalid`, () => {
expect(checker.satisfiedBy(ExtendedVersion.parse("2:0"))).toEqual(false)
expect(checker.satisfiedBy(ExtendedVersion.parse("1.2.3:5"))).toEqual(
false,
)
expect(checker.satisfiedBy(ExtendedVersion.parse("1.2.3:4.1"))).toEqual(
false,
)
expect(checker.satisfiedBy(ExtendedVersion.parse("1.2.3:4"))).toEqual(
false,
)
})
test(`VersionRange.parse("<1.2.3:4") valid`, () => {
expect(checker.satisfiedBy(ExtendedVersion.parse("1.2.3:0"))).toEqual(
true,
)
expect(checker.satisfiedBy(ExtendedVersion.parse("1:0"))).toEqual(true)
})
}
{
const checker = VersionRange.parse("<=1.2.3:4")
test(`VersionRange.parse("<=1.2.3:4") invalid`, () => {
expect(checker.satisfiedBy(ExtendedVersion.parse("2:0"))).toEqual(false)
expect(checker.satisfiedBy(ExtendedVersion.parse("1.2.3:5"))).toEqual(
false,
)
expect(checker.satisfiedBy(ExtendedVersion.parse("1.2.3:4.1"))).toEqual(
false,
)
})
test(`VersionRange.parse("<=1.2.3:4") valid`, () => {
expect(checker.satisfiedBy(ExtendedVersion.parse("1.2.3:0"))).toEqual(
true,
)
expect(checker.satisfiedBy(ExtendedVersion.parse("1:0"))).toEqual(true)
expect(checker.satisfiedBy(ExtendedVersion.parse("1.2.3:4"))).toEqual(
true,
)
})
}
{
const checkA = VersionRange.parse(">1")
const checkB = VersionRange.parse("<=2")
const checker = checkA.and(checkB)
test(`simple and(checkers) valid`, () => {
expect(checker.satisfiedBy(ExtendedVersion.parse("2:0"))).toEqual(true)
expect(checker.satisfiedBy(ExtendedVersion.parse("1.1:0"))).toEqual(
true,
)
})
test(`simple and(checkers) invalid`, () => {
expect(checker.satisfiedBy(ExtendedVersion.parse("2.1:0"))).toEqual(
false,
)
expect(checker.satisfiedBy(ExtendedVersion.parse("1:0"))).toEqual(false)
expect(checker.satisfiedBy(ExtendedVersion.parse("1:0"))).toEqual(false)
})
}
{
const checkA = VersionRange.parse("<1")
const checkB = VersionRange.parse("=2")
const checker = checkA.or(checkB)
test(`simple or(checkers) valid`, () => {
expect(checker.satisfiedBy(ExtendedVersion.parse("2:0"))).toEqual(true)
expect(checker.satisfiedBy(ExtendedVersion.parse("0.1:0"))).toEqual(
true,
)
})
test(`simple or(checkers) invalid`, () => {
expect(checker.satisfiedBy(ExtendedVersion.parse("2.1:0"))).toEqual(
false,
)
expect(checker.satisfiedBy(ExtendedVersion.parse("1:0"))).toEqual(false)
expect(checker.satisfiedBy(ExtendedVersion.parse("1.1:0"))).toEqual(
false,
)
})
}
{
const checker = VersionRange.parse("~1.2")
test(`VersionRange.parse(~1.2) valid`, () => {
expect(checker.satisfiedBy(ExtendedVersion.parse("1.2:0"))).toEqual(
true,
)
expect(checker.satisfiedBy(ExtendedVersion.parse("1.2.1:0"))).toEqual(
true,
)
})
test(`VersionRange.parse(~1.2) invalid`, () => {
expect(checker.satisfiedBy(ExtendedVersion.parse("1.3:0"))).toEqual(
false,
)
expect(checker.satisfiedBy(ExtendedVersion.parse("1.3.1:0"))).toEqual(
false,
)
expect(checker.satisfiedBy(ExtendedVersion.parse("1.1.1:0"))).toEqual(
false,
)
expect(checker.satisfiedBy(ExtendedVersion.parse("1.1:0"))).toEqual(
false,
)
expect(checker.satisfiedBy(ExtendedVersion.parse("1:0"))).toEqual(false)
expect(checker.satisfiedBy(ExtendedVersion.parse("2:0"))).toEqual(false)
})
}
{
const checker = VersionRange.parse("~1.2").not()
test(`VersionRange.parse(~1.2).not() valid`, () => {
expect(checker.satisfiedBy(ExtendedVersion.parse("1.3:0"))).toEqual(
true,
)
expect(checker.satisfiedBy(ExtendedVersion.parse("1.3.1:0"))).toEqual(
true,
)
expect(checker.satisfiedBy(ExtendedVersion.parse("1.1.1:0"))).toEqual(
true,
)
expect(checker.satisfiedBy(ExtendedVersion.parse("1.1:0"))).toEqual(
true,
)
expect(checker.satisfiedBy(ExtendedVersion.parse("1:0"))).toEqual(true)
expect(checker.satisfiedBy(ExtendedVersion.parse("2:0"))).toEqual(true)
})
test(`VersionRange.parse(~1.2).not() invalid `, () => {
expect(checker.satisfiedBy(ExtendedVersion.parse("1.2:0"))).toEqual(
false,
)
expect(checker.satisfiedBy(ExtendedVersion.parse("1.2.1:0"))).toEqual(
false,
)
})
}
{
const checker = VersionRange.parse("!~1.2")
test(`!(VersionRange.parse(~1.2)) valid`, () => {
expect(checker.satisfiedBy(ExtendedVersion.parse("1.3:0"))).toEqual(
true,
)
expect(checker.satisfiedBy(ExtendedVersion.parse("1.3.1:0"))).toEqual(
true,
)
expect(checker.satisfiedBy(ExtendedVersion.parse("1.1.1:0"))).toEqual(
true,
)
expect(checker.satisfiedBy(ExtendedVersion.parse("1.1:0"))).toEqual(
true,
)
expect(checker.satisfiedBy(ExtendedVersion.parse("1:0"))).toEqual(true)
expect(checker.satisfiedBy(ExtendedVersion.parse("2:0"))).toEqual(true)
})
test(`!(VersionRange.parse(~1.2)) invalid `, () => {
expect(checker.satisfiedBy(ExtendedVersion.parse("1.2:0"))).toEqual(
false,
)
expect(checker.satisfiedBy(ExtendedVersion.parse("1.2.1:0"))).toEqual(
false,
)
})
}
{
const checker = VersionRange.parse("!>1.2.3:4")
test(`VersionRange.parse("!>1.2.3:4") invalid`, () => {
expect(checker.satisfiedBy(ExtendedVersion.parse("2:0"))).toEqual(false)
expect(checker.satisfiedBy(ExtendedVersion.parse("1.2.3:5"))).toEqual(
false,
)
expect(checker.satisfiedBy(ExtendedVersion.parse("1.2.3:4.1"))).toEqual(
false,
)
})
test(`VersionRange.parse("!>1.2.3:4") valid`, () => {
expect(checker.satisfiedBy(ExtendedVersion.parse("1.2.3:4"))).toEqual(
true,
)
expect(checker.satisfiedBy(ExtendedVersion.parse("1.2.3:0"))).toEqual(
true,
)
expect(checker.satisfiedBy(ExtendedVersion.parse("1:0"))).toEqual(true)
})
}
{
test(">1 && =1.2", () => {
const checker = VersionRange.parse(">1 && =1.2")
expect(checker.satisfiedBy(ExtendedVersion.parse("1.2:0"))).toEqual(
true,
)
expect(checker.satisfiedBy(ExtendedVersion.parse("1.2.1:0"))).toEqual(
false,
)
})
test("=1 || =2", () => {
const checker = VersionRange.parse("=1 || =2")
expect(checker.satisfiedBy(ExtendedVersion.parse("1:0"))).toEqual(true)
expect(checker.satisfiedBy(ExtendedVersion.parse("2:0"))).toEqual(true)
expect(checker.satisfiedBy(ExtendedVersion.parse("3:0"))).toEqual(false)
})
test(">1 && =1.2 || =2", () => {
const checker = VersionRange.parse(">1 && =1.2 || =2")
expect(checker.satisfiedBy(ExtendedVersion.parse("1.2:0"))).toEqual(
true,
)
expect(checker.satisfiedBy(ExtendedVersion.parse("1:0"))).toEqual(false)
expect(checker.satisfiedBy(ExtendedVersion.parse("2:0"))).toEqual(true)
expect(checker.satisfiedBy(ExtendedVersion.parse("3:0"))).toEqual(false)
})
test("&& before || order of operationns: <1.5 && >1 || >1.5 && <3", () => {
const checker = VersionRange.parse("<1.5 && >1 || >1.5 && <3")
expect(checker.satisfiedBy(ExtendedVersion.parse("1.1:0"))).toEqual(
true,
)
expect(checker.satisfiedBy(ExtendedVersion.parse("2:0"))).toEqual(true)
expect(checker.satisfiedBy(ExtendedVersion.parse("1.5:0"))).toEqual(
false,
)
expect(checker.satisfiedBy(ExtendedVersion.parse("1:0"))).toEqual(false)
expect(checker.satisfiedBy(ExtendedVersion.parse("3:0"))).toEqual(false)
})
test("Compare function on the emver", () => {
const a = ExtendedVersion.parse("1.2.3:0")
const b = ExtendedVersion.parse("1.2.4:0")
expect(a.compare(b)).toEqual("less")
expect(b.compare(a)).toEqual("greater")
expect(a.compare(a)).toEqual("equal")
})
test("Compare for sort function on the emver", () => {
const a = ExtendedVersion.parse("1.2.3:0")
const b = ExtendedVersion.parse("1.2.4:0")
expect(a.compareForSort(b)).toEqual(-1)
expect(b.compareForSort(a)).toEqual(1)
expect(a.compareForSort(a)).toEqual(0)
})
}
}
})

View File

@@ -1,148 +0,0 @@
import { Graph } from "../util/graph"
describe("graph", () => {
{
{
test("findVertex", () => {
const graph = new Graph<string, string>()
const foo = graph.addVertex("foo", [], [])
const bar = graph.addVertex(
"bar",
[{ from: foo, metadata: "foo-bar" }],
[],
)
const baz = graph.addVertex(
"baz",
[{ from: bar, metadata: "bar-baz" }],
[],
)
const qux = graph.addVertex(
"qux",
[{ from: baz, metadata: "baz-qux" }],
[],
)
const match = Array.from(graph.findVertex((v) => v.metadata === "qux"))
expect(match).toHaveLength(1)
expect(match[0]).toBe(qux)
})
test("shortestPathA", () => {
const graph = new Graph<string, string>()
const foo = graph.addVertex("foo", [], [])
const bar = graph.addVertex(
"bar",
[{ from: foo, metadata: "foo-bar" }],
[],
)
const baz = graph.addVertex(
"baz",
[{ from: bar, metadata: "bar-baz" }],
[],
)
const qux = graph.addVertex(
"qux",
[{ from: baz, metadata: "baz-qux" }],
[],
)
graph.addEdge("foo-qux", foo, qux)
expect(graph.shortestPath(foo, qux) || []).toHaveLength(1)
})
test("shortestPathB", () => {
const graph = new Graph<string, string>()
const foo = graph.addVertex("foo", [], [])
const bar = graph.addVertex(
"bar",
[{ from: foo, metadata: "foo-bar" }],
[],
)
const baz = graph.addVertex(
"baz",
[{ from: bar, metadata: "bar-baz" }],
[],
)
const qux = graph.addVertex(
"qux",
[{ from: baz, metadata: "baz-qux" }],
[],
)
graph.addEdge("bar-qux", bar, qux)
expect(graph.shortestPath(foo, qux) || []).toHaveLength(2)
})
test("shortestPathC", () => {
const graph = new Graph<string, string>()
const foo = graph.addVertex("foo", [], [])
const bar = graph.addVertex(
"bar",
[{ from: foo, metadata: "foo-bar" }],
[],
)
const baz = graph.addVertex(
"baz",
[{ from: bar, metadata: "bar-baz" }],
[],
)
const qux = graph.addVertex(
"qux",
[{ from: baz, metadata: "baz-qux" }],
[{ to: foo, metadata: "qux-foo" }],
)
expect(graph.shortestPath(foo, qux) || []).toHaveLength(3)
})
test("bfs", () => {
const graph = new Graph<string, string>()
const foo = graph.addVertex("foo", [], [])
const bar = graph.addVertex(
"bar",
[{ from: foo, metadata: "foo-bar" }],
[],
)
const baz = graph.addVertex(
"baz",
[{ from: bar, metadata: "bar-baz" }],
[],
)
const qux = graph.addVertex(
"qux",
[
{ from: foo, metadata: "foo-qux" },
{ from: baz, metadata: "baz-qux" },
],
[],
)
const bfs = Array.from(graph.breadthFirstSearch(foo))
expect(bfs).toHaveLength(4)
expect(bfs[0]).toBe(foo)
expect(bfs[1]).toBe(bar)
expect(bfs[2]).toBe(qux)
expect(bfs[3]).toBe(baz)
})
test("reverseBfs", () => {
const graph = new Graph<string, string>()
const foo = graph.addVertex("foo", [], [])
const bar = graph.addVertex(
"bar",
[{ from: foo, metadata: "foo-bar" }],
[],
)
const baz = graph.addVertex(
"baz",
[{ from: bar, metadata: "bar-baz" }],
[],
)
const qux = graph.addVertex(
"qux",
[
{ from: foo, metadata: "foo-qux" },
{ from: baz, metadata: "baz-qux" },
],
[],
)
const bfs = Array.from(graph.reverseBreadthFirstSearch(qux))
expect(bfs).toHaveLength(4)
expect(bfs[0]).toBe(qux)
expect(bfs[1]).toBe(foo)
expect(bfs[2]).toBe(baz)
expect(bfs[3]).toBe(bar)
})
}
}
})

View File

@@ -1,17 +0,0 @@
import { containsAddress } from "../health/checkFns/checkPortListening"
describe("Health ready check", () => {
it("Should be able to parse an example information", () => {
let input = `
sl local_address rem_address st tx_queue rx_queue tr tm->when retrnsmt uid timeout inode
0: 00000000:1F90 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 21634478 1 0000000000000000 100 0 0 10 0
1: 00000000:0050 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 21634477 1 0000000000000000 100 0 0 10 0
2: 0B00007F:9671 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 21635458 1 0000000000000000 100 0 0 10 0
3: 00000000:0D73 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 21634479 1 0000000000000000 100 0 0 10 0
`
expect(containsAddress(input, 80)).toBe(true)
expect(containsAddress(input, 1234)).toBe(false)
})
})

View File

@@ -1,30 +0,0 @@
import { ServiceInterfaceBuilder } from "../interfaces/ServiceInterfaceBuilder"
import { Effects } from "../types"
import { sdk } from "./output.sdk"
describe("host", () => {
test("Testing that the types work", () => {
async function test(effects: Effects) {
const foo = sdk.host.multi(effects, "foo")
const fooOrigin = await foo.bindPort(80, {
protocol: "http" as const,
preferredExternalPort: 80,
})
const fooInterface = new ServiceInterfaceBuilder({
effects,
name: "Foo",
id: "foo",
description: "A Foo",
hasPrimary: false,
type: "ui",
username: "bar",
path: "/baz",
search: { qux: "yes" },
schemeOverride: null,
masked: false,
})
await fooOrigin.export([fooInterface])
}
})
})

View File

@@ -1,428 +0,0 @@
import { oldSpecToBuilder } from "../../scripts/oldSpecToBuilder"
oldSpecToBuilder(
// Make the location
"./lib/test/output.ts",
// Put the config here
{
mediasources: {
type: "list",
subtype: "enum",
name: "Media Sources",
description: "List of Media Sources to use with Jellyfin",
range: "[1,*)",
default: ["nextcloud"],
spec: {
values: ["nextcloud", "filebrowser"],
"value-names": {
nextcloud: "NextCloud",
filebrowser: "File Browser",
},
},
},
testListUnion: {
type: "list",
subtype: "union",
name: "Lightning Nodes",
description: "List of Lightning Network node instances to manage",
range: "[1,*)",
default: ["lnd"],
spec: {
type: "string",
"display-as": "{{name}}",
"unique-by": "name",
name: "Node Implementation",
tag: {
id: "type",
name: "Type",
description:
"- LND: Lightning Network Daemon from Lightning Labs\n- CLN: Core Lightning from Blockstream\n",
"variant-names": {
lnd: "Lightning Network Daemon (LND)",
"c-lightning": "Core Lightning (CLN)",
},
},
default: "lnd",
variants: {
lnd: {
name: {
type: "string",
name: "Node Name",
description: "Name of this node in the list",
default: "LND Wrapper",
nullable: false,
},
},
},
},
},
rpc: {
type: "object",
name: "RPC Settings",
description: "RPC configuration options.",
spec: {
enable: {
type: "boolean",
name: "Enable",
description: "Allow remote RPC requests.",
default: true,
},
username: {
type: "string",
nullable: false,
name: "Username",
description: "The username for connecting to Bitcoin over RPC.",
default: "bitcoin",
masked: true,
pattern: "^[a-zA-Z0-9_]+$",
"pattern-description":
"Must be alphanumeric (can contain underscore).",
},
password: {
type: "string",
nullable: false,
name: "RPC Password",
description: "The password for connecting to Bitcoin over RPC.",
default: {
charset: "a-z,2-7",
len: 20,
},
pattern: '^[^\\n"]*$',
"pattern-description":
"Must not contain newline or quote characters.",
copyable: true,
masked: true,
},
bio: {
type: "string",
nullable: false,
name: "Username",
description: "The username for connecting to Bitcoin over RPC.",
default: "bitcoin",
masked: true,
pattern: "^[a-zA-Z0-9_]+$",
"pattern-description":
"Must be alphanumeric (can contain underscore).",
textarea: true,
},
advanced: {
type: "object",
name: "Advanced",
description: "Advanced RPC Settings",
spec: {
auth: {
name: "Authorization",
description:
"Username and hashed password for JSON-RPC connections. RPC clients connect using the usual http basic authentication.",
type: "list",
subtype: "string",
default: [],
spec: {
pattern:
"^[a-zA-Z0-9_-]+:([0-9a-fA-F]{2})+\\$([0-9a-fA-F]{2})+$",
"pattern-description":
'Each item must be of the form "<USERNAME>:<SALT>$<HASH>".',
masked: false,
},
range: "[0,*)",
},
serialversion: {
name: "Serialization Version",
description:
"Return raw transaction or block hex with Segwit or non-SegWit serialization.",
type: "enum",
values: ["non-segwit", "segwit"],
"value-names": {},
default: "segwit",
},
servertimeout: {
name: "Rpc Server Timeout",
description:
"Number of seconds after which an uncompleted RPC call will time out.",
type: "number",
nullable: false,
range: "[5,300]",
integral: true,
units: "seconds",
default: 30,
},
threads: {
name: "Threads",
description:
"Set the number of threads for handling RPC calls. You may wish to increase this if you are making lots of calls via an integration.",
type: "number",
nullable: false,
default: 16,
range: "[1,64]",
integral: true,
},
workqueue: {
name: "Work Queue",
description:
"Set the depth of the work queue to service RPC calls. Determines how long the backlog of RPC requests can get before it just rejects new ones.",
type: "number",
nullable: false,
default: 128,
range: "[8,256]",
integral: true,
units: "requests",
},
},
},
},
},
"zmq-enabled": {
type: "boolean",
name: "ZeroMQ Enabled",
description: "Enable the ZeroMQ interface",
default: true,
},
txindex: {
type: "boolean",
name: "Transaction Index",
description: "Enable the Transaction Index (txindex)",
default: true,
},
wallet: {
type: "object",
name: "Wallet",
description: "Wallet Settings",
spec: {
enable: {
name: "Enable Wallet",
description: "Load the wallet and enable wallet RPC calls.",
type: "boolean",
default: true,
},
avoidpartialspends: {
name: "Avoid Partial Spends",
description:
"Group outputs by address, selecting all or none, instead of selecting on a per-output basis. This improves privacy at the expense of higher transaction fees.",
type: "boolean",
default: true,
},
discardfee: {
name: "Discard Change Tolerance",
description:
"The fee rate (in BTC/kB) that indicates your tolerance for discarding change by adding it to the fee.",
type: "number",
nullable: false,
default: 0.0001,
range: "[0,.01]",
integral: false,
units: "BTC/kB",
},
},
},
advanced: {
type: "object",
name: "Advanced",
description: "Advanced Settings",
spec: {
mempool: {
type: "object",
name: "Mempool",
description: "Mempool Settings",
spec: {
mempoolfullrbf: {
name: "Enable Full RBF",
description:
"Policy for your node to use for relaying and mining unconfirmed transactions. For details, see https://github.com/bitcoin/bitcoin/blob/master/doc/release-notes/release-notes-24.0.md#notice-of-new-option-for-transaction-replacement-policies",
type: "boolean",
default: false,
},
persistmempool: {
type: "boolean",
name: "Persist Mempool",
description: "Save the mempool on shutdown and load on restart.",
default: true,
},
maxmempool: {
type: "number",
nullable: false,
name: "Max Mempool Size",
description:
"Keep the transaction memory pool below <n> megabytes.",
range: "[1,*)",
integral: true,
units: "MiB",
default: 300,
},
mempoolexpiry: {
type: "number",
nullable: false,
name: "Mempool Expiration",
description:
"Do not keep transactions in the mempool longer than <n> hours.",
range: "[1,*)",
integral: true,
units: "Hr",
default: 336,
},
},
},
peers: {
type: "object",
name: "Peers",
description: "Peer Connection Settings",
spec: {
listen: {
type: "boolean",
name: "Make Public",
description:
"Allow other nodes to find your server on the network.",
default: true,
},
onlyconnect: {
type: "boolean",
name: "Disable Peer Discovery",
description: "Only connect to specified peers.",
default: false,
},
onlyonion: {
type: "boolean",
name: "Disable Clearnet",
description: "Only connect to peers over Tor.",
default: false,
},
addnode: {
name: "Add Nodes",
description: "Add addresses of nodes to connect to.",
type: "list",
subtype: "object",
range: "[0,*)",
default: [],
spec: {
"unique-by": null,
spec: {
hostname: {
type: "string",
nullable: true,
name: "Hostname",
description: "Domain or IP address of bitcoin peer",
pattern:
"(^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$)|((^(([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:)|fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}|::(ffff(:0{1,4}){0,1}:){0,1}((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])|([0-9a-fA-F]{1,4}:){1,4}:((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]))$)|(^[a-z2-7]{16}\\.onion$)|(^([a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?\\.)+[a-z0-9][a-z0-9-]{0,61}[a-z0-9]$))",
"pattern-description":
"Must be either a domain name, or an IPv4 or IPv6 address. Do not include protocol scheme (eg 'http://') or port.",
masked: false,
},
port: {
type: "number",
nullable: true,
name: "Port",
description:
"Port that peer is listening on for inbound p2p connections",
range: "[0,65535]",
integral: true,
},
},
},
},
},
},
dbcache: {
type: "number",
nullable: true,
name: "Database Cache",
description:
"How much RAM to allocate for caching the TXO set. Higher values improve syncing performance, but increase your chance of using up all your system's memory or corrupting your database in the event of an ungraceful shutdown. Set this high but comfortably below your system's total RAM during IBD, then turn down to 450 (or leave blank) once the sync completes.",
warning:
"WARNING: Increasing this value results in a higher chance of ungraceful shutdowns, which can leave your node unusable if it happens during the initial block download. Use this setting with caution. Be sure to set this back to the default (450 or leave blank) once your node is synced. DO NOT press the STOP button if your dbcache is large. Instead, set this number back to the default, hit save, and wait for bitcoind to restart on its own.",
range: "(0,*)",
integral: true,
units: "MiB",
},
pruning: {
type: "union",
name: "Pruning Settings",
description:
"Blockchain Pruning Options\nReduce the blockchain size on disk\n",
warning:
"If you set pruning to Manual and your disk is smaller than the total size of the blockchain, you MUST have something running that prunes these blocks or you may overfill your disk!\nDisabling pruning will convert your node into a full archival node. This requires a resync of the entire blockchain, a process that may take several days. Make sure you have enough free disk space or you may fill up your disk.\n",
tag: {
id: "mode",
name: "Pruning Mode",
description:
'- Disabled: Disable pruning\n- Automatic: Limit blockchain size on disk to a certain number of megabytes\n- Manual: Prune blockchain with the "pruneblockchain" RPC\n',
"variant-names": {
disabled: "Disabled",
automatic: "Automatic",
manual: "Manual",
},
},
variants: {
disabled: {},
automatic: {
size: {
type: "number",
nullable: false,
name: "Max Chain Size",
description: "Limit of blockchain size on disk.",
warning:
"Increasing this value will require re-syncing your node.",
default: 550,
range: "[550,1000000)",
integral: true,
units: "MiB",
},
},
manual: {
size: {
type: "number",
nullable: false,
name: "Failsafe Chain Size",
description: "Prune blockchain if size expands beyond this.",
default: 65536,
range: "[550,1000000)",
integral: true,
units: "MiB",
},
},
},
default: "disabled",
},
blockfilters: {
type: "object",
name: "Block Filters",
description: "Settings for storing and serving compact block filters",
spec: {
blockfilterindex: {
type: "boolean",
name: "Compute Compact Block Filters (BIP158)",
description:
"Generate Compact Block Filters during initial sync (IBD) to enable 'getblockfilter' RPC. This is useful if dependent services need block filters to efficiently scan for addresses/transactions etc.",
default: true,
},
peerblockfilters: {
type: "boolean",
name: "Serve Compact Block Filters to Peers (BIP157)",
description:
"Serve Compact Block Filters as a peer service to other nodes on the network. This is useful if you wish to connect an SPV client to your node to make it efficient to scan transactions without having to download all block data. 'Compute Compact Block Filters (BIP158)' is required.",
default: false,
},
},
},
bloomfilters: {
type: "object",
name: "Bloom Filters (BIP37)",
description: "Setting for serving Bloom Filters",
spec: {
peerbloomfilters: {
type: "boolean",
name: "Serve Bloom Filters to Peers",
description:
"Peers have the option of setting filters on each connection they make after the version handshake has completed. Bloom filters are for clients implementing SPV (Simplified Payment Verification) that want to check that block headers connect together correctly, without needing to verify the full blockchain. The client must trust that the transactions in the chain are in fact valid. It is highly recommended AGAINST using for anything except Bisq integration.",
warning:
"This is ONLY for use with Bisq integration, please use Block Filters for all other applications.",
default: false,
},
},
},
},
},
},
{
// convert this to `start-sdk/lib` for conversions
StartSdk: "./output.sdk",
},
)

View File

@@ -1,56 +0,0 @@
import { StartSdk } from "../StartSdk"
import { setupManifest } from "../manifest/setupManifest"
import { VersionInfo } from "../version/VersionInfo"
import { VersionGraph } from "../version/VersionGraph"
export type Manifest = any
export const sdk = StartSdk.of()
.withManifest(
setupManifest(
VersionGraph.of(
VersionInfo.of({
version: "1.0.0:0",
releaseNotes: "",
migrations: {},
})
.satisfies("#other:1.0.0:0")
.satisfies("#other:2.0.0:0"),
),
{
id: "testOutput",
title: "",
license: "",
replaces: [],
wrapperRepo: "",
upstreamRepo: "",
supportSite: "",
marketingSite: "",
donationUrl: null,
description: {
short: "",
long: "",
},
containers: {},
images: {},
volumes: [],
assets: [],
alerts: {
install: null,
update: null,
uninstall: null,
restore: null,
start: null,
stop: null,
},
dependencies: {
"remote-test": {
description: "",
optional: false,
s9pk: "https://example.com/remote-test.s9pk",
},
},
},
),
)
.withStore<{ storeRoot: { storeLeaf: "value" } }>()
.build(true)

View File

@@ -1,143 +0,0 @@
import { ConfigSpec, matchConfigSpec } from "./output"
import * as _I from "../index"
import { camelCase } from "../../scripts/oldSpecToBuilder"
import { deepMerge } from "../util/deepMerge"
export type IfEquals<T, U, Y = unknown, N = never> =
(<G>() => G extends T ? 1 : 2) extends <G>() => G extends U ? 1 : 2 ? Y : N
export function testOutput<A, B>(): (c: IfEquals<A, B>) => null {
return () => null
}
/// Testing the types of the input spec
testOutput<ConfigSpec["rpc"]["enable"], boolean>()(null)
testOutput<ConfigSpec["rpc"]["username"], string>()(null)
testOutput<ConfigSpec["rpc"]["username"], string>()(null)
testOutput<ConfigSpec["rpc"]["advanced"]["auth"], string[]>()(null)
testOutput<
ConfigSpec["rpc"]["advanced"]["serialversion"],
"segwit" | "non-segwit"
>()(null)
testOutput<ConfigSpec["rpc"]["advanced"]["servertimeout"], number>()(null)
testOutput<
ConfigSpec["advanced"]["peers"]["addnode"][0]["hostname"],
string | null | undefined
>()(null)
testOutput<ConfigSpec["testListUnion"][0]["union"]["value"]["name"], string>()(
null,
)
testOutput<ConfigSpec["testListUnion"][0]["union"]["selection"], "lnd">()(null)
testOutput<ConfigSpec["mediasources"], Array<"filebrowser" | "nextcloud">>()(
null,
)
// @ts-expect-error Because enable should be a boolean
testOutput<ConfigSpec["rpc"]["enable"], string>()(null)
// prettier-ignore
// @ts-expect-error Expect that the string is the one above
testOutput<ConfigSpec["testListUnion"][0]['selection']['selection'], "selection">()(null);
/// Here we test the output of the matchConfigSpec function
describe("Inputs", () => {
const validInput: ConfigSpec = {
mediasources: ["filebrowser"],
testListUnion: [
{
union: { selection: "lnd", value: { name: "string" } },
},
],
rpc: {
enable: true,
bio: "This is a bio",
username: "test",
password: "test",
advanced: {
auth: ["test"],
serialversion: "segwit",
servertimeout: 6,
threads: 3,
workqueue: 9,
},
},
"zmq-enabled": false,
txindex: false,
wallet: { enable: false, avoidpartialspends: false, discardfee: 0.0001 },
advanced: {
mempool: {
maxmempool: 1,
persistmempool: true,
mempoolexpiry: 23,
mempoolfullrbf: true,
},
peers: {
listen: true,
onlyconnect: true,
onlyonion: true,
addnode: [
{
hostname: "test",
port: 1,
},
],
},
dbcache: 5,
pruning: {
selection: "disabled",
value: {},
},
blockfilters: {
blockfilterindex: false,
peerblockfilters: false,
},
bloomfilters: { peerbloomfilters: false },
},
}
test("test valid input", () => {
const output = matchConfigSpec.unsafeCast(validInput)
expect(output).toEqual(validInput)
})
test("test no longer care about the conversion of min/max and validating", () => {
matchConfigSpec.unsafeCast(
deepMerge({}, validInput, { rpc: { advanced: { threads: 0 } } }),
)
})
test("test errors should throw for number in string", () => {
expect(() =>
matchConfigSpec.unsafeCast(
deepMerge({}, validInput, { rpc: { enable: 2 } }),
),
).toThrowError()
})
test("Test that we set serialversion to something not segwit or non-segwit", () => {
expect(() =>
matchConfigSpec.unsafeCast(
deepMerge({}, validInput, {
rpc: { advanced: { serialversion: "testing" } },
}),
),
).toThrowError()
})
})
describe("camelCase", () => {
test("'EquipmentClass name'", () => {
expect(camelCase("EquipmentClass name")).toEqual("equipmentClassName")
})
test("'Equipment className'", () => {
expect(camelCase("Equipment className")).toEqual("equipmentClassName")
})
test("'equipment class name'", () => {
expect(camelCase("equipment class name")).toEqual("equipmentClassName")
})
test("'Equipment Class Name'", () => {
expect(camelCase("Equipment Class Name")).toEqual("equipmentClassName")
})
test("'hyphen-name-format'", () => {
expect(camelCase("hyphen-name-format")).toEqual("hyphenNameFormat")
})
test("'underscore_name_format'", () => {
expect(camelCase("underscore_name_format")).toEqual("underscoreNameFormat")
})
})

377
sdk/lib/test/output.ts Normal file
View File

@@ -0,0 +1,377 @@
import { sdk } from "./output.sdk"
const {Config, List, Value, Variants} = sdk
export const configSpec = Config.of({"mediasources": Value.multiselect({
"name": "Media Sources",
"minLength": null,
"maxLength": null,
"default": [
"nextcloud"
],
"description": "List of Media Sources to use with Jellyfin",
"warning": null,
"values": {
"nextcloud": "NextCloud",
"filebrowser": "File Browser"
}
}),"testListUnion": Value.list(/* TODO: Convert range for this value ([1,*))*/List.obj({
name:"Lightning Nodes",
minLength:null,
maxLength:null,
default: [],
description: "List of Lightning Network node instances to manage",
warning: null,
}, {
spec:
Config.of({
"union": /* TODO: Convert range for this value ([1,*))*/
Value.union({
name: "Type",
description: "- LND: Lightning Network Daemon from Lightning Labs\n- CLN: Core Lightning from Blockstream\n",
warning: null,
required: {"default":"lnd"},
}, Variants.of({"lnd": {name: "lnd", spec: Config.of({"name": Value.text({
"name": "Node Name",
"required": {
"default": "LND Wrapper"
},
"description": "Name of this node in the list",
"warning": null,
"masked": false,
"placeholder": null,
"inputmode": "text",
"patterns": [],
"minLength": null,
"maxLength": null
}),})},}))
})
,
displayAs: "{{name}}",
uniqueBy: "name",
})),"rpc": Value.object({
name: "RPC Settings",
description: "RPC configuration options.",
warning: null,
}, Config.of({"enable": Value.toggle({
"name": "Enable",
"default": true,
"description": "Allow remote RPC requests.",
"warning": null
}),"username": Value.text({
"name": "Username",
"required": {
"default": "bitcoin"
},
"description": "The username for connecting to Bitcoin over RPC.",
"warning": null,
"masked": true,
"placeholder": null,
"inputmode": "text",
"patterns": [
{
"regex": "^[a-zA-Z0-9_]+$",
"description": "Must be alphanumeric (can contain underscore)."
}
],
"minLength": null,
"maxLength": null
}),"password": Value.text({
"name": "RPC Password",
"required": {
"default": {
"charset": "a-z,2-7",
"len": 20
}
},
"description": "The password for connecting to Bitcoin over RPC.",
"warning": null,
"masked": true,
"placeholder": null,
"inputmode": "text",
"patterns": [
{
"regex": "^[^\\n\"]*$",
"description": "Must not contain newline or quote characters."
}
],
"minLength": null,
"maxLength": null
}),"bio": Value.textarea({
"name": "Username",
"description": "The username for connecting to Bitcoin over RPC.",
"warning": null,
"required": true,
"placeholder": null,
"maxLength": null,
"minLength": null
}),"advanced": Value.object({
name: "Advanced",
description: "Advanced RPC Settings",
warning: null,
}, Config.of({"auth": Value.list(/* TODO: Convert range for this value ([0,*))*/List.text({
"name": "Authorization",
"minLength": null,
"maxLength": null,
"default": [],
"description": "Username and hashed password for JSON-RPC connections. RPC clients connect using the usual http basic authentication.",
"warning": null
}, {"masked":false,"placeholder":null,"patterns":[{"regex":"^[a-zA-Z0-9_-]+:([0-9a-fA-F]{2})+\\$([0-9a-fA-F]{2})+$","description":"Each item must be of the form \"<USERNAME>:<SALT>$<HASH>\"."}],"minLength":null,"maxLength":null})),"serialversion": Value.select({
"name": "Serialization Version",
"description": "Return raw transaction or block hex with Segwit or non-SegWit serialization.",
"warning": null,
"required": {
"default": "segwit"
},
"values": {
"non-segwit": "non-segwit",
"segwit": "segwit"
}
} as const),"servertimeout": /* TODO: Convert range for this value ([5,300])*/Value.number({
"name": "Rpc Server Timeout",
"description": "Number of seconds after which an uncompleted RPC call will time out.",
"warning": null,
"required": {
"default": 30
},
"min": null,
"max": null,
"step": null,
"integer": true,
"units": "seconds",
"placeholder": null
}),"threads": /* TODO: Convert range for this value ([1,64])*/Value.number({
"name": "Threads",
"description": "Set the number of threads for handling RPC calls. You may wish to increase this if you are making lots of calls via an integration.",
"warning": null,
"required": {
"default": 16
},
"min": null,
"max": null,
"step": null,
"integer": true,
"units": null,
"placeholder": null
}),"workqueue": /* TODO: Convert range for this value ([8,256])*/Value.number({
"name": "Work Queue",
"description": "Set the depth of the work queue to service RPC calls. Determines how long the backlog of RPC requests can get before it just rejects new ones.",
"warning": null,
"required": {
"default": 128
},
"min": null,
"max": null,
"step": null,
"integer": true,
"units": "requests",
"placeholder": null
}),})),})),"zmq-enabled": Value.toggle({
"name": "ZeroMQ Enabled",
"default": true,
"description": "Enable the ZeroMQ interface",
"warning": null
}),"txindex": Value.toggle({
"name": "Transaction Index",
"default": true,
"description": "Enable the Transaction Index (txindex)",
"warning": null
}),"wallet": Value.object({
name: "Wallet",
description: "Wallet Settings",
warning: null,
}, Config.of({"enable": Value.toggle({
"name": "Enable Wallet",
"default": true,
"description": "Load the wallet and enable wallet RPC calls.",
"warning": null
}),"avoidpartialspends": Value.toggle({
"name": "Avoid Partial Spends",
"default": true,
"description": "Group outputs by address, selecting all or none, instead of selecting on a per-output basis. This improves privacy at the expense of higher transaction fees.",
"warning": null
}),"discardfee": /* TODO: Convert range for this value ([0,.01])*/Value.number({
"name": "Discard Change Tolerance",
"description": "The fee rate (in BTC/kB) that indicates your tolerance for discarding change by adding it to the fee.",
"warning": null,
"required": {
"default": 0.0001
},
"min": null,
"max": null,
"step": null,
"integer": false,
"units": "BTC/kB",
"placeholder": null
}),})),"advanced": Value.object({
name: "Advanced",
description: "Advanced Settings",
warning: null,
}, Config.of({"mempool": Value.object({
name: "Mempool",
description: "Mempool Settings",
warning: null,
}, Config.of({"mempoolfullrbf": Value.toggle({
"name": "Enable Full RBF",
"default": false,
"description": "Policy for your node to use for relaying and mining unconfirmed transactions. For details, see https://github.com/bitcoin/bitcoin/blob/master/doc/release-notes/release-notes-24.0.md#notice-of-new-option-for-transaction-replacement-policies",
"warning": null
}),"persistmempool": Value.toggle({
"name": "Persist Mempool",
"default": true,
"description": "Save the mempool on shutdown and load on restart.",
"warning": null
}),"maxmempool": /* TODO: Convert range for this value ([1,*))*/Value.number({
"name": "Max Mempool Size",
"description": "Keep the transaction memory pool below <n> megabytes.",
"warning": null,
"required": {
"default": 300
},
"min": null,
"max": null,
"step": null,
"integer": true,
"units": "MiB",
"placeholder": null
}),"mempoolexpiry": /* TODO: Convert range for this value ([1,*))*/Value.number({
"name": "Mempool Expiration",
"description": "Do not keep transactions in the mempool longer than <n> hours.",
"warning": null,
"required": {
"default": 336
},
"min": null,
"max": null,
"step": null,
"integer": true,
"units": "Hr",
"placeholder": null
}),})),"peers": Value.object({
name: "Peers",
description: "Peer Connection Settings",
warning: null,
}, Config.of({"listen": Value.toggle({
"name": "Make Public",
"default": true,
"description": "Allow other nodes to find your server on the network.",
"warning": null
}),"onlyconnect": Value.toggle({
"name": "Disable Peer Discovery",
"default": false,
"description": "Only connect to specified peers.",
"warning": null
}),"onlyonion": Value.toggle({
"name": "Disable Clearnet",
"default": false,
"description": "Only connect to peers over Tor.",
"warning": null
}),"addnode": Value.list(/* TODO: Convert range for this value ([0,*))*/List.obj({
name: "Add Nodes",
minLength: null,
maxLength: null,
default: [],
description: "Add addresses of nodes to connect to.",
warning: null,
}, {
spec: Config.of({"hostname": Value.text({
"name": "Hostname",
"required": false,
"description": "Domain or IP address of bitcoin peer",
"warning": null,
"masked": false,
"placeholder": null,
"inputmode": "text",
"patterns": [
{
"regex": "(^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$)|((^(([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:)|fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}|::(ffff(:0{1,4}){0,1}:){0,1}((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])|([0-9a-fA-F]{1,4}:){1,4}:((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]))$)|(^[a-z2-7]{16}\\.onion$)|(^([a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?\\.)+[a-z0-9][a-z0-9-]{0,61}[a-z0-9]$))",
"description": "Must be either a domain name, or an IPv4 or IPv6 address. Do not include protocol scheme (eg 'http://') or port."
}
],
"minLength": null,
"maxLength": null
}),"port": /* TODO: Convert range for this value ([0,65535])*/Value.number({
"name": "Port",
"description": "Port that peer is listening on for inbound p2p connections",
"warning": null,
"required": false,
"min": null,
"max": null,
"step": null,
"integer": true,
"units": null,
"placeholder": null
}),}),
displayAs: null,
uniqueBy: null,
})),})),"dbcache": /* TODO: Convert range for this value ((0,*))*/Value.number({
"name": "Database Cache",
"description": "How much RAM to allocate for caching the TXO set. Higher values improve syncing performance, but increase your chance of using up all your system's memory or corrupting your database in the event of an ungraceful shutdown. Set this high but comfortably below your system's total RAM during IBD, then turn down to 450 (or leave blank) once the sync completes.",
"warning": "WARNING: Increasing this value results in a higher chance of ungraceful shutdowns, which can leave your node unusable if it happens during the initial block download. Use this setting with caution. Be sure to set this back to the default (450 or leave blank) once your node is synced. DO NOT press the STOP button if your dbcache is large. Instead, set this number back to the default, hit save, and wait for bitcoind to restart on its own.",
"required": false,
"min": null,
"max": null,
"step": null,
"integer": true,
"units": "MiB",
"placeholder": null
}),"pruning": Value.union({
name: "Pruning Settings",
description: "- Disabled: Disable pruning\n- Automatic: Limit blockchain size on disk to a certain number of megabytes\n- Manual: Prune blockchain with the \"pruneblockchain\" RPC\n",
warning: null,
// prettier-ignore
required: {"default":"disabled"},
}, Variants.of({"disabled": {name: "Disabled", spec: Config.of({})},"automatic": {name: "Automatic", spec: Config.of({"size": /* TODO: Convert range for this value ([550,1000000))*/Value.number({
"name": "Max Chain Size",
"description": "Limit of blockchain size on disk.",
"warning": "Increasing this value will require re-syncing your node.",
"required": {
"default": 550
},
"min": null,
"max": null,
"step": null,
"integer": true,
"units": "MiB",
"placeholder": null
}),})},"manual": {name: "Manual", spec: Config.of({"size": /* TODO: Convert range for this value ([550,1000000))*/Value.number({
"name": "Failsafe Chain Size",
"description": "Prune blockchain if size expands beyond this.",
"warning": null,
"required": {
"default": 65536
},
"min": null,
"max": null,
"step": null,
"integer": true,
"units": "MiB",
"placeholder": null
}),})},})),"blockfilters": Value.object({
name: "Block Filters",
description: "Settings for storing and serving compact block filters",
warning: null,
}, Config.of({"blockfilterindex": Value.toggle({
"name": "Compute Compact Block Filters (BIP158)",
"default": true,
"description": "Generate Compact Block Filters during initial sync (IBD) to enable 'getblockfilter' RPC. This is useful if dependent services need block filters to efficiently scan for addresses/transactions etc.",
"warning": null
}),"peerblockfilters": Value.toggle({
"name": "Serve Compact Block Filters to Peers (BIP157)",
"default": false,
"description": "Serve Compact Block Filters as a peer service to other nodes on the network. This is useful if you wish to connect an SPV client to your node to make it efficient to scan transactions without having to download all block data. 'Compute Compact Block Filters (BIP158)' is required.",
"warning": null
}),})),"bloomfilters": Value.object({
name: "Bloom Filters (BIP37)",
description: "Setting for serving Bloom Filters",
warning: null,
}, Config.of({"peerbloomfilters": Value.toggle({
"name": "Serve Bloom Filters to Peers",
"default": false,
"description": "Peers have the option of setting filters on each connection they make after the version handshake has completed. Bloom filters are for clients implementing SPV (Simplified Payment Verification) that want to check that block headers connect together correctly, without needing to verify the full blockchain. The client must trust that the transactions in the chain are in fact valid. It is highly recommended AGAINST using for anything except Bisq integration.",
"warning": "This is ONLY for use with Bisq integration, please use Block Filters for all other applications."
}),})),})),});
export const matchConfigSpec = configSpec.validator;
export type ConfigSpec = typeof matchConfigSpec._TYPE;

View File

@@ -1,27 +0,0 @@
import { sdk } from "./output.sdk"
describe("setupDependencyConfig", () => {
test("test", () => {
const testConfig = sdk.Config.of({
test: sdk.Value.text({
name: "testValue",
required: false,
}),
})
const testConfig2 = sdk.Config.of({
test2: sdk.Value.text({
name: "testValue2",
required: false,
}),
})
const remoteTest = sdk.DependencyConfig.of({
localConfigSpec: testConfig,
remoteConfigSpec: testConfig2,
dependencyConfig: async ({}) => {},
})
sdk.setupDependencyConfig(testConfig, {
"remote-test": remoteTest,
})
})
})

View File

@@ -1,89 +0,0 @@
import { Effects } from "../types"
import {
CheckDependenciesParam,
ExecuteAction,
GetConfiguredParams,
GetStoreParams,
SetDataVersionParams,
SetMainStatus,
SetStoreParams,
} from ".././osBindings"
import { CreateSubcontainerFsParams } from ".././osBindings"
import { DestroySubcontainerFsParams } from ".././osBindings"
import { BindParams } from ".././osBindings"
import { GetHostInfoParams } from ".././osBindings"
import { SetConfigured } from ".././osBindings"
import { SetHealth } from ".././osBindings"
import { ExposeForDependentsParams } from ".././osBindings"
import { GetSslCertificateParams } from ".././osBindings"
import { GetSslKeyParams } from ".././osBindings"
import { GetServiceInterfaceParams } from ".././osBindings"
import { SetDependenciesParams } from ".././osBindings"
import { GetSystemSmtpParams } from ".././osBindings"
import { GetServicePortForwardParams } from ".././osBindings"
import { ExportServiceInterfaceParams } from ".././osBindings"
import { GetPrimaryUrlParams } from ".././osBindings"
import { ListServiceInterfacesParams } from ".././osBindings"
import { ExportActionParams } from ".././osBindings"
import { MountParams } from ".././osBindings"
import { StringObject } from "../util"
function typeEquality<ExpectedType>(_a: ExpectedType) {}
type WithCallback<T> = Omit<T, "callback"> & { callback: () => void }
type EffectsTypeChecker<T extends StringObject = Effects> = {
[K in keyof T]: T[K] extends (args: infer A) => any
? A
: T[K] extends StringObject
? EffectsTypeChecker<T[K]>
: never
}
describe("startosTypeValidation ", () => {
test(`checking the params match`, () => {
const testInput: any = {}
typeEquality<EffectsTypeChecker>({
executeAction: {} as ExecuteAction,
subcontainer: {
createFs: {} as CreateSubcontainerFsParams,
destroyFs: {} as DestroySubcontainerFsParams,
},
clearBindings: undefined,
getInstalledPackages: undefined,
bind: {} as BindParams,
getHostInfo: {} as WithCallback<GetHostInfoParams>,
getConfigured: {} as GetConfiguredParams,
restart: undefined,
shutdown: undefined,
setConfigured: {} as SetConfigured,
setDataVersion: {} as SetDataVersionParams,
getDataVersion: undefined,
setHealth: {} as SetHealth,
exposeForDependents: {} as ExposeForDependentsParams,
getSslCertificate: {} as WithCallback<GetSslCertificateParams>,
getSslKey: {} as GetSslKeyParams,
getServiceInterface: {} as WithCallback<GetServiceInterfaceParams>,
setDependencies: {} as SetDependenciesParams,
store: {
get: {} as any, // as GetStoreParams,
set: {} as any, // as SetStoreParams,
},
getSystemSmtp: {} as WithCallback<GetSystemSmtpParams>,
getContainerIp: undefined,
getServicePortForward: {} as GetServicePortForwardParams,
clearServiceInterfaces: undefined,
exportServiceInterface: {} as ExportServiceInterfaceParams,
getPrimaryUrl: {} as WithCallback<GetPrimaryUrlParams>,
listServiceInterfaces: {} as WithCallback<ListServiceInterfacesParams>,
exportAction: {} as ExportActionParams,
clearActions: undefined,
mount: {} as MountParams,
checkDependencies: {} as CheckDependenciesParam,
getDependencies: undefined,
setMainStatus: {} as SetMainStatus,
})
typeEquality<Parameters<Effects["executeAction"]>[0]>(
testInput as ExecuteAction,
)
})
})

View File

@@ -1,114 +0,0 @@
import { MainEffects, StartSdk } from "../StartSdk"
import { extractJsonPath } from "../store/PathBuilder"
import { Effects } from "../types"
type Store = {
config: {
someValue: "a" | "b"
}
}
type Manifest = any
const todo = <A>(): A => {
throw new Error("not implemented")
}
const noop = () => {}
const sdk = StartSdk.of()
.withManifest({} as Manifest)
.withStore<Store>()
.build(true)
const storePath = sdk.StorePath
describe("Store", () => {
test("types", async () => {
;async () => {
sdk.store.setOwn(todo<Effects>(), storePath.config, {
someValue: "a",
})
sdk.store.setOwn(todo<Effects>(), storePath.config.someValue, "b")
sdk.store.setOwn(todo<Effects>(), storePath, {
config: { someValue: "b" },
})
sdk.store.setOwn(
todo<Effects>(),
storePath.config.someValue,
// @ts-expect-error Type is wrong for the setting value
5,
)
sdk.store.setOwn(
todo<Effects>(),
// @ts-expect-error Path is wrong
"/config/someVae3lue",
"someValue",
)
todo<Effects>().store.set<Store>({
path: extractJsonPath(storePath.config.someValue),
value: "b",
})
todo<Effects>().store.set<Store, "/config/some2Value">({
path: extractJsonPath(storePath.config.someValue),
//@ts-expect-error Path is wrong
value: "someValueIn",
})
;(await sdk.store
.getOwn(todo<MainEffects>(), storePath.config.someValue)
.const()) satisfies string
;(await sdk.store
.getOwn(todo<MainEffects>(), storePath.config)
.const()) satisfies Store["config"]
await sdk.store // @ts-expect-error Path is wrong
.getOwn(todo<MainEffects>(), "/config/somdsfeValue")
.const()
/// ----------------- ERRORS -----------------
sdk.store.setOwn(todo<MainEffects>(), storePath, {
// @ts-expect-error Type is wrong for the setting value
config: { someValue: "notInAOrB" },
})
sdk.store.setOwn(
todo<MainEffects>(),
sdk.StorePath.config.someValue,
// @ts-expect-error Type is wrong for the setting value
"notInAOrB",
)
;(await sdk.store
.getOwn(todo<Effects>(), storePath.config.someValue)
// @ts-expect-error Const should normally not be callable
.const()) satisfies string
;(await sdk.store
.getOwn(todo<Effects>(), storePath.config)
// @ts-expect-error Const should normally not be callable
.const()) satisfies Store["config"]
await sdk.store // @ts-expect-error Path is wrong
.getOwn("/config/somdsfeValue")
// @ts-expect-error Const should normally not be callable
.const()
///
;(await sdk.store
.getOwn(todo<MainEffects>(), storePath.config.someValue)
// @ts-expect-error satisfies type is wrong
.const()) satisfies number
await sdk.store // @ts-expect-error Path is wrong
.getOwn(todo<MainEffects>(), extractJsonPath(storePath.config))
.const()
;(await todo<Effects>().store.get({
path: extractJsonPath(storePath.config.someValue),
callback: noop,
})) satisfies string
await todo<Effects>().store.get<Store, "/config/someValue">({
// @ts-expect-error Path is wrong as in it doesn't match above
path: "/config/someV2alue",
callback: noop,
})
await todo<Effects>().store.get<Store, "/config/someV2alue">({
// @ts-expect-error Path is wrong as in it doesn't exists in wrapper type
path: "/config/someV2alue",
callback: noop,
})
}
})
})

View File

@@ -1,26 +0,0 @@
import { deepEqual } from "../util/deepEqual"
import { deepMerge } from "../util/deepMerge"
describe("deepMerge", () => {
test("deepMerge({}, {a: 1}, {b: 2}) should return {a: 1, b: 2}", () => {
expect(deepMerge({}, { a: 1 }, { b: 2 })).toEqual({ a: 1, b: 2 })
})
test("deepMerge(null, [1,2,3]) should equal [1,2,3]", () => {
expect(deepMerge(null, [1, 2, 3])).toEqual([1, 2, 3])
})
test("deepMerge({a: {b: 1, c:2}}, {a: {b: 3}}) should equal {a: {b: 3, c: 2}}", () => {
expect(deepMerge({ a: { b: 1, c: 2 } }, { a: { b: 3 } })).toEqual({
a: { b: 3, c: 2 },
})
})
test("deepMerge({a: {b: 1, c:2}}, {a: {b: 3}}) should equal {a: {b: 3, c: 2}} with deep equal", () => {
expect(
deepEqual(deepMerge({ a: { b: 1, c: 2 } }, { a: { b: 3 } }), {
a: { b: 3, c: 2 },
}),
).toBeTruthy()
})
test("deepMerge([1,2,3], [2,3,4]) should equal [2,3,4]", () => {
expect(deepMerge([1, 2, 3], [2, 3, 4])).toEqual([2, 3, 4])
})
})

View File

@@ -1,20 +0,0 @@
import { getHostname } from "../util/getServiceInterface"
describe("getHostname ", () => {
const inputToExpected = [
["http://localhost:3000", "localhost"],
["http://localhost", "localhost"],
["localhost", "localhost"],
["http://127.0.0.1/", "127.0.0.1"],
["http://127.0.0.1/testing/1234?314345", "127.0.0.1"],
["127.0.0.1/", "127.0.0.1"],
["http://mail.google.com/", "mail.google.com"],
["mail.google.com/", "mail.google.com"],
]
for (const [input, expectValue] of inputToExpected) {
test(`should return ${expectValue} for ${input}`, () => {
expect(getHostname(input)).toEqual(expectValue)
})
}
})