Improve tests

This commit is contained in:
Arnaud 2025-05-30 11:32:00 +02:00
parent d5ff634879
commit 5f1227fce3
No known key found for this signature in database
GPG Key ID: B8FBC178F10CA7AE
7 changed files with 483 additions and 419 deletions

View File

@ -26,4 +26,13 @@ jobs:
- run: npm ci
- name: Start codex-factory
run: npx codex-factory start latest &
- name: Wait for client node to be started
run: npx wait-on tcp:8080 --timeout=300000
- name: Wait for first storage provider to be started
run: npx wait-on tcp:8081 --timeout=300000
- run: npm test

124
src/data/data.spec.ts Normal file
View File

@ -0,0 +1,124 @@
import { assert, describe, it } from "vitest";
import { CodexData } from "./data";
import { NodeUploadStategy } from "./node-upload";
import crypto from "crypto";
describe("data", () => {
const data = new CodexData(
process.env["CLIENT_URL"] || "http://localhost:8080"
);
const spData = new CodexData(
process.env["SP_URL"] || "http://localhost:8081"
);
it("uploads a file a download it locally", async () => {
const content = crypto.randomBytes(16).toString("hex");
const strategy = new NodeUploadStategy(content);
const res = data.upload(strategy);
const cid = await res.result;
assert.ok(cid.error == false);
assert.ok(cid.data);
const cids = await data.cids();
assert.ok(cids.error == false);
assert.ok(cids.data.content.find((c) => c.cid == cid.data));
const localDownload = await data.localDownload(cid.data);
assert.ok(localDownload.error == false);
assert.strictEqual(await localDownload.data.text(), content);
const manifest = await data.fetchManifest(cid.data);
assert.ok(manifest.error == false);
assert.strictEqual(manifest.data.cid, cid.data);
const { blockSize, datasetSize, treeCid } = manifest.data.manifest;
assert.ok(blockSize);
assert.ok(datasetSize);
assert.ok(treeCid);
});
it("updates the space available when storing data", async () => {
const content = crypto.randomBytes(16).toString("hex");
let space = await data.space();
assert.ok(space.error == false);
assert.ok(space.data.quotaMaxBytes);
const usedBytes = space.data.quotaUsedBytes;
const strategy = new NodeUploadStategy(content);
const res = data.upload(strategy);
const cid = await res.result;
assert.ok(cid.error == false);
assert.ok(cid.data);
space = await data.space();
assert.ok(space.error == false);
assert.ok(space.data.quotaMaxBytes);
assert.ok(space.data.quotaUsedBytes > usedBytes);
});
it("stream downloads a file on the network", async () => {
const content = crypto.randomBytes(16).toString("hex");
const strategy = new NodeUploadStategy(content);
const res = spData.upload(strategy);
const cid = await res.result;
assert.ok(cid.error == false);
assert.ok(cid.data);
const networkDownload = await data.networkDownloadStream(cid.data);
assert.ok(networkDownload.error == false);
assert.strictEqual(await networkDownload.data.text(), content);
});
it("downloads a file on the network", async () => {
const content = crypto.randomBytes(16).toString("hex");
const strategy = new NodeUploadStategy(content);
const res = spData.upload(strategy);
const cid = await res.result;
assert.ok(cid.error == false);
assert.ok(cid.data);
const networkDownload = await data.networkDownload(cid.data);
assert.ok(networkDownload.error == false);
const cids = await data.cids();
assert.ok(cids.error == false);
assert.ok(cids.data.content.find((c) => c.cid == cid.data));
});
it("returns an error when trying to stream download a not existing file on the network", async () => {
const cid = crypto.randomBytes(16).toString("hex");
const networkDownload = await data.networkDownloadStream(cid);
assert.ok(networkDownload.error);
assert.strictEqual(networkDownload.data.message, "Incorrect Cid");
});
it("returns an error when trying to download a not existing file on the network", async () => {
const cid = crypto.randomBytes(16).toString("hex");
const networkDownload = await data.networkDownload(cid);
assert.ok(networkDownload.error);
assert.strictEqual(networkDownload.data.message, "Incorrect Cid");
});
it("returns an error when trying to download a not existing file locally", async () => {
const cid = crypto.randomBytes(16).toString("hex");
const networkDownload = await data.localDownload(cid);
assert.ok(networkDownload.error);
assert.strictEqual(networkDownload.data.message, "Incorrect Cid");
});
it("returns an error when trying to fetch a not existing manifest", async () => {
const cid = crypto.randomBytes(16).toString("hex");
const fetchManifest = await data.fetchManifest(cid);
assert.ok(fetchManifest.error);
assert.strictEqual(fetchManifest.data.message, "Incorrect Cid");
});
});

View File

@ -1,45 +1,26 @@
import { afterEach, assert, describe, it, vi } from "vitest";
import { assert, describe, it } from "vitest";
import { CodexDebug } from "./debug";
import type { CodexLogLevel } from "./types";
import { CodexError } from "../errors/errors";
describe("debug", () => {
afterEach(() => {
vi.restoreAllMocks();
const debug = new CodexDebug(
process.env["CLIENT_URL"] || "http://localhost:8080"
);
it("changes the log level", async () => {
const logLevel = await debug.setLogLevel("NOTICE");
assert.ok(logLevel.error == false);
});
const debug = new CodexDebug("http://localhost:3000");
it("returns an error when trying to setup the log level with a bad value", async () => {
const response = await debug.setLogLevel("TEST" as CodexLogLevel);
assert.deepStrictEqual(response, {
error: true,
data: new CodexError("Cannot validate the input", {
errors: [
{
expected:
'"TRACE" | "DEBUG" | "INFO" | "NOTICE" | "WARN" | "ERROR" | "FATAL"',
message:
'Invalid type: Expected "TRACE" | "DEBUG" | "INFO" | "NOTICE" | "WARN" | "ERROR" | "FATAL" but received "TEST"',
path: undefined,
received: '"TEST"',
},
],
}),
});
it("gets the debug info", async () => {
const info = await debug.info();
assert.ok(info.error == false);
assert.ok(info.data.spr);
assert.ok(info.data.announceAddresses.length > 0);
});
it("returns a success when trying to setup the log level with a correct value", async () => {
const mockResponse = {
ok: true,
status: 200,
text: async () => "",
} as Response;
globalThis.fetch = vi.fn().mockResolvedValue(mockResponse);
const response = await debug.setLogLevel("ERROR");
assert.deepStrictEqual(response, { error: false, data: "" });
it("returns error when changing the log level with wrong value", async () => {
const logLevel = await debug.setLogLevel("HELLO");
assert.ok(logLevel.error);
assert.strictEqual(logLevel.data.message, "Cannot validate the input");
});
});

View File

@ -1,381 +1,280 @@
import { afterEach, assert, describe, it, vi } from "vitest";
import { Fetch } from "../fetch-safe/fetch-safe";
import { assert, describe, it } from "vitest";
import { CodexMarketplace } from "./marketplace";
import {
randomEthereumAddress,
randomInt,
randomString,
} from "../tests/tests.util";
import { CodexError } from "../errors/errors";
import { CodexData } from "../data/data";
import { NodeUploadStategy } from "../data/node-upload";
import type {
CodexAvailabilityPatchInput,
CodexCreateAvailabilityInput,
CodexCreateStorageRequestInput,
} from "./types";
function createStorageRequest() {
return {
cid: randomString(64),
duration: randomInt(1, 64000),
pricePerBytePerSecond: randomInt(1, 100),
proofProbability: randomInt(1, 100),
nodes: randomInt(1, 5),
tolerance: randomInt(1, 100),
expiry: randomInt(1, 100),
collateralPerByte: randomInt(1, 100),
};
}
function missingNumberValidationError(field: string) {
return {
error: true as any,
data: new CodexError("Cannot validate the input", {
errors: [
{
path: field,
expected: "number",
message: "Invalid type: Expected number but received undefined",
received: "undefined",
},
],
}),
};
}
function extraValidationError(field: string, value: unknown) {
return {
error: true as any,
data: new CodexError("Cannot validate the input", {
errors: [
{
path: field,
expected: "never",
message: `Invalid type: Expected never but received "${value}"`,
received: `"${value}"`,
},
],
}),
};
}
function missingStringValidationError(field: string) {
return {
error: true as any,
data: new CodexError("Cannot validate the input", {
errors: [
{
path: field,
expected: "string",
message: "Invalid type: Expected string but received undefined",
received: "undefined",
},
],
}),
};
}
function mistypeNumberValidationError(field: string, value: string) {
return {
error: true as any,
data: new CodexError("Cannot validate the input", {
errors: [
{
path: field,
expected: "number",
message: `Invalid type: Expected number but received "${value}"`,
received: `"${value}"`,
},
],
}),
};
}
function minNumberValidationError(field: string, min: number) {
return {
error: true as any,
data: new CodexError("Cannot validate the input", {
errors: [
{
path: field,
expected: ">=" + min,
message: "Invalid value: Expected >=1 but received 0",
received: "0",
},
],
}),
};
}
function createAvailability() {
return {
id: randomEthereumAddress(),
totalSize: randomInt(0, 9),
duration: randomInt(0, 9),
minPrice: randomInt(0, 9),
minPricePerBytePerSecond: randomInt(0, 9),
totalCollateral: randomInt(0, 900),
totalRemainingCollateral: randomInt(0, 900),
};
}
describe("marketplace", () => {
const marketplace = new CodexMarketplace("http://localhost:3000");
afterEach(() => {
vi.restoreAllMocks();
});
it("returns an error when trying to create an availability without total size", async () => {
const response = await marketplace.createAvailability({
duration: 3000,
totalCollateral: 1,
minPricePerBytePerSecond: 100,
} as any);
assert.deepStrictEqual(response, missingNumberValidationError("totalSize"));
});
it("returns an error when trying to create an availability with an invalid number valid", async () => {
const response = await marketplace.createAvailability({
duration: 3000,
totalCollateral: 1,
minPricePerBytePerSecond: 100,
totalSize: "abc",
} as any);
assert.deepStrictEqual(
response,
mistypeNumberValidationError("totalSize", "abc")
describe("marketplace", async () => {
describe("availability", async () => {
const spMarketplace = new CodexMarketplace(
process.env["SP_URL"] || "http://localhost:8081"
);
});
const totalSize = 1_000_000;
const duration = 3000;
const minPricePerBytePerSecond = 1000;
const totalCollateral = 1_000_000_000;
it("returns an error when trying to create an availability with zero total size", async () => {
const response = await marketplace.createAvailability({
duration: 3000,
totalCollateral: 1,
minPricePerBytePerSecond: 100,
totalSize: 0,
const body = {
duration,
totalCollateral,
minPricePerBytePerSecond,
totalSize,
};
const result = await spMarketplace.createAvailability(body);
assert.ok(result.error == false);
const availability = result.data;
describe("create", async () => {
it("verifies that the availability was created successfully", async () => {
assert.ok(availability.id);
assert.strictEqual(availability.duration, duration);
assert.strictEqual(availability.freeSize, totalSize);
assert.strictEqual(
availability.minPricePerBytePerSecond,
minPricePerBytePerSecond
);
assert.strictEqual(availability.totalCollateral, totalCollateral);
assert.strictEqual(
availability.totalRemainingCollateral,
totalCollateral
);
assert.strictEqual(availability.totalSize, totalSize);
assert.strictEqual(availability.until, 0);
assert.ok(availability.enabled);
});
const errors: Partial<CodexCreateAvailabilityInput>[] = [
{ duration: 0 },
{ totalSize: 0 },
{ totalCollateral: -1 },
{ minPricePerBytePerSecond: -1 },
];
for (const err of errors) {
const field = Object.keys(err)[0] as keyof typeof err;
assert.ok(field);
it(`fails to create availability with wrong ${field}`, async () => {
const response = await spMarketplace.createAvailability({
...body,
[field]: err[field],
});
assert.ok(response.error);
assert.ok(response.data.errors?.length);
assert.equal(response.data.errors[0]?.path, field);
assert.equal(
response.data.errors[0]?.received,
err[field]?.toString()
);
assert.ok(
response.data.errors[0]?.message.startsWith("Invalid value:")
);
});
}
});
assert.deepStrictEqual(response, minNumberValidationError("totalSize", 1));
describe("update", async () => {
async function getUpdatedAvailability() {
const availabilities = await spMarketplace.availabilities();
assert.ok(availabilities.error == false);
return availabilities.data.find((a) => a.id == availability.id);
}
const updates: Omit<CodexAvailabilityPatchInput, "id">[] = [
{ enabled: false },
{ duration: 3000 },
{ minPricePerBytePerSecond: 1 },
{ totalSize: 3000 },
{ totalCollateral: 3000 },
{ until: 5000 },
];
for (const usecase of updates) {
const field = Object.keys(usecase)[0] as keyof typeof usecase;
assert.ok(field);
it(`updates availability's ${field}`, async () => {
const response = await spMarketplace.updateAvailability({
id: availability.id,
...usecase,
});
assert.ok(response.error == false);
const updated = await getUpdatedAvailability();
assert.ok(updated?.[field] == usecase[field]);
});
}
const errors: Omit<CodexAvailabilityPatchInput, "id">[] = [
{ duration: 0 },
{ totalSize: 0 },
{ totalCollateral: -1 },
{ minPricePerBytePerSecond: -1 },
{ until: -1 },
];
for (const err of errors) {
const field = Object.keys(err)[0] as keyof typeof err;
assert.ok(field);
it(`fails to update availability with wrong ${field}`, async () => {
const response = await spMarketplace.updateAvailability({
id: availability.id,
...err,
});
assert.ok(response.error);
assert.ok(response.data.errors?.length);
assert.equal(response.data.errors[0]?.path, field);
assert.equal(
response.data.errors[0]?.received,
err[field]?.toString()
);
assert.ok(
response.data.errors[0]?.message.startsWith("Invalid value:")
);
});
}
});
});
it("returns an error when trying to create an availability without duration", async () => {
const response = await marketplace.createAvailability({
totalSize: 3000,
totalCollateral: 1,
minPricePerBytePerSecond: 100,
} as any);
const data = new CodexData(
process.env["CLIENT_URL"] || "http://localhost:8080"
);
const marketplace = new CodexMarketplace(
process.env["CLIENT_URL"] || "http://localhost:8080"
);
assert.deepStrictEqual(response, missingNumberValidationError("duration"));
});
async function uploadContent(sizeInBytes: number) {
const content = "a".repeat(sizeInBytes);
const strategy = new NodeUploadStategy(content);
const res = data.upload(strategy);
const cid = await res.result;
assert.ok(cid.error == false);
assert.ok(cid.data);
return cid.data;
}
it("returns an error when trying to create an availability with zero duration", async () => {
const response = await marketplace.createAvailability({
duration: 0,
totalCollateral: 1,
minPricePerBytePerSecond: 100,
totalSize: 3000,
async function createStorageRequestBody(targetSizeInBytes = 131072) {
return {
cid: await uploadContent(targetSizeInBytes),
duration: 1000,
pricePerBytePerSecond: 1,
proofProbability: 1,
expiry: 900,
collateralPerByte: 1,
nodes: 3,
tolerance: 1,
};
}
describe("storage request", async () => {
const body = await createStorageRequestBody();
it("creates successfully", async () => {
const request = await marketplace.createStorageRequest(body);
assert.ok(request.error == false);
assert.ok(request.data);
});
assert.deepStrictEqual(response, minNumberValidationError("duration", 1));
const errors: {
request: Partial<CodexCreateStorageRequestInput>;
message: string;
}[] = [
{ request: { cid: "" }, message: "Incorrect Cid" },
{
request: { duration: 0 },
message: "Cannot validate the input",
},
{
request: { pricePerBytePerSecond: 0 },
message: "Cannot validate the input",
},
{
request: { proofProbability: 0 },
message: "Cannot validate the input",
},
{
request: { expiry: 0 },
message: "Cannot validate the input",
},
{
request: { collateralPerByte: 0 },
message: "Cannot validate the input",
},
{
request: { tolerance: 0 },
message: "Cannot validate the input",
},
{
request: { cid: await uploadContent(1) },
message:
"Dataset too small for erasure parameters, need at least 131072 bytes",
},
{
request: { duration: 3000, expiry: 4000 },
message:
"Expiry must be greater than zero and less than the request's duration",
},
{
request: { nodes: 2, tolerance: 1 },
message:
"Invalid parameters: parameters must satify `1 < (nodes - tolerance) ≥ tolerance`",
},
];
for (const err of errors) {
it(`fails to create storage request with wrong ${JSON.stringify(err.request)}`, async () => {
const request = await marketplace.createStorageRequest({
...body,
...err.request,
});
assert.ok(request.error);
assert.ok(request.data.message.includes(err.message));
if (request.data.errors?.length) {
const keys = Object.keys(err.request);
for (const e of request.data.errors) {
assert.ok(e.path);
assert.ok(keys.includes(e.path));
}
}
});
}
});
it("returns an error when trying to create an availability without min price", async () => {
const response = await marketplace.createAvailability({
totalSize: 3000,
totalCollateral: 1,
duration: 100,
} as any);
describe("purchases", async () => {
const body = await createStorageRequestBody();
assert.deepStrictEqual(response, missingNumberValidationError("minPrice"));
});
const request = await marketplace.createStorageRequest(body);
assert.ok(request.error == false);
assert.ok(request.data);
it("returns an error when trying to create an availability without max collateral", async () => {
const response = await marketplace.createAvailability({
totalSize: 3000,
minPricePerBytePerSecond: 100,
duration: 100,
} as any);
it("lists successfully", async () => {
const ids = await marketplace.purchaseIds();
assert.deepStrictEqual(
response,
missingNumberValidationError("maxCollateral")
);
});
assert.ok(ids.error == false);
assert.ok(ids.data.length);
assert.ok(ids.data[0]);
it("returns an error when trying to create an availability with an extra field", async () => {
const response = await marketplace.createAvailability({
maxCollateral: 1,
totalSize: 3000,
minPricePerBytePerSecond: 100,
duration: 100,
hello: "world",
} as any);
const purchase = await marketplace.purchaseDetail(ids.data[0]);
assert.ok(purchase.error == false);
assert.ok(purchase.data.requestId);
assert.ok(purchase.data.state);
assert.deepStrictEqual(response, extraValidationError("hello", "world"));
});
it("returns a response when the request succeed", async () => {
const data = { ...createAvailability(), freeSize: 1000 };
const spy = vi.spyOn(Fetch, "safeJson");
spy.mockImplementationOnce(() => Promise.resolve({ error: false, data }));
const response = await marketplace.createAvailability({
totalCollateral: 1,
totalSize: 3000,
minPricePerBytePerSecond: 100,
duration: 100,
const purchases = await marketplace.purchases();
assert.ok(purchases.error == false);
assert.ok(purchases.data.length);
assert.ok(purchases.data[0]?.requestId);
assert.ok(purchases.data[0]?.state);
});
assert.ok(!response.error);
// @ts-ignore
assert.deepEqual(response.data, data);
});
it("returns a response when the create availability succeed", async () => {
const data = { ...createAvailability(), freeSize: 1000 };
const spy = vi.spyOn(Fetch, "safeJson");
spy.mockImplementationOnce(() => Promise.resolve({ error: false, data }));
const response = await marketplace.createAvailability({
totalCollateral: data.totalCollateral,
totalSize: data.totalSize,
minPricePerBytePerSecond: data.minPricePerBytePerSecond,
duration: data.duration,
});
assert.ok(!response.error);
// @ts-ignore
assert.deepEqual(response.data, data);
});
it("returns an error when trying to update an availability without id", async () => {
const response = await marketplace.updateAvailability({
totalCollateral: 1,
totalSize: 3000,
minPricePerBytePerSecond: 100,
duration: 100,
} as any);
assert.deepStrictEqual(response, missingStringValidationError("id"));
});
it("returns an error when trying to update an availability with zero total size", async () => {
const response = await marketplace.updateAvailability({
id: randomString(64),
totalSize: 0,
minPricePerBytePerSecond: 100,
duration: 100,
totalCollateral: 100,
});
assert.deepStrictEqual(response, minNumberValidationError("totalSize", 1));
});
it("returns an error when trying to update an availability with zero duration", async () => {
const response = await marketplace.updateAvailability({
id: randomString(64),
totalSize: 100,
duration: 0,
minPricePerBytePerSecond: 100,
totalCollateral: 100,
});
assert.deepStrictEqual(response, minNumberValidationError("duration", 1));
});
it("returns a response when the update availability succeed", async () => {
const mockResponse = {
ok: true,
status: 200,
} as any;
globalThis.fetch = vi.fn().mockResolvedValue(mockResponse);
const response = await marketplace.updateAvailability({
id: randomString(64),
totalSize: 3000,
duration: 10,
minPricePerBytePerSecond: 100,
totalCollateral: 100,
});
assert.ok(!response.error);
});
it("returns an error when trying to create a storage request without cid", async () => {
const { cid, ...rest } = createStorageRequest();
const response = await marketplace.createStorageRequest(rest as any);
assert.deepStrictEqual(response, missingStringValidationError("cid"));
});
it("returns an error when trying to create a storage request without duration", async () => {
const { duration, ...rest } = createStorageRequest();
const response = await marketplace.createStorageRequest(rest as any);
assert.deepStrictEqual(response, missingNumberValidationError("duration"));
});
it("returns an error when trying to create a storage request with zero duration", async () => {
const { duration, ...rest } = createStorageRequest();
const response = await marketplace.createStorageRequest({
...rest,
duration: 0,
});
assert.deepStrictEqual(response, minNumberValidationError("duration", 1));
});
it("returns an error when trying to create a storage request without pricePerBytePerSecond", async () => {
const { pricePerBytePerSecond, ...rest } = createStorageRequest();
const response = await marketplace.createStorageRequest(rest as any);
assert.deepStrictEqual(
response,
missingNumberValidationError("pricePerBytePerSecond")
);
});
it("returns an error when trying to create a storage request without proof probability", async () => {
const { proofProbability, ...rest } = createStorageRequest();
const response = await marketplace.createStorageRequest(rest as any);
assert.deepStrictEqual(
response,
missingNumberValidationError("proofProbability")
);
});
it("returns an error when trying to create a storage request without expiry", async () => {
const { expiry, ...rest } = createStorageRequest();
const response = await marketplace.createStorageRequest(rest as any);
assert.deepStrictEqual(response, missingNumberValidationError("expiry"));
});
it("returns an error when trying to create a storage request with zero expiry", async () => {
const { expiry, ...rest } = createStorageRequest();
const response = await marketplace.createStorageRequest({
...rest,
expiry: 0,
});
assert.deepStrictEqual(response, minNumberValidationError("expiry", 1));
});
it("returns an error when trying to create a storage request without collateralPerByte", async () => {
const { collateralPerByte, ...rest } = createStorageRequest();
const response = await marketplace.createStorageRequest(rest as any);
assert.deepStrictEqual(
response,
missingNumberValidationError("collateralPerByte")
);
});
});

View File

@ -25,6 +25,7 @@ import {
type CodexStorageRequestCreateBody,
type CodexReservation,
type CodexPurchaseWithoutTypes,
type CodexAvailabilityPatchBody,
} from "./types";
import {
CodexCreateAvailabilityInput,
@ -179,15 +180,27 @@ export class CodexMarketplace {
const url =
this.url + Api.config.prefix + "/sales/availability/" + result.output.id;
const body: CodexAvailabilityCreateBody = {
totalSize: result.output.totalSize,
duration: result.output.duration,
minPricePerBytePerSecond:
result.output.minPricePerBytePerSecond.toString(),
totalCollateral: result.output.totalCollateral.toString(),
};
const { totalSize, duration, minPricePerBytePerSecond, totalCollateral } =
result.output;
let body: CodexAvailabilityPatchBody = {};
if (result.output.enabled) {
if (totalSize) {
body.totalSize = totalSize;
}
if (duration) {
body.duration = duration;
}
if (minPricePerBytePerSecond) {
body.minPricePerBytePerSecond = minPricePerBytePerSecond.toString();
}
if (totalCollateral) {
body.totalCollateral = totalCollateral.toString();
}
if (result.output.enabled != undefined) {
body.enabled = result.output.enabled;
}

View File

@ -45,19 +45,21 @@ export type CodexAvailabilityCreateBody = Exclude<
export const CodexCreateAvailabilityInput = v.strictObject({
totalSize: v.pipe(v.number(), v.minValue(1)),
duration: v.pipe(v.number(), v.minValue(1)),
minPricePerBytePerSecond: v.number(),
totalCollateral: v.number(),
minPricePerBytePerSecond: v.pipe(v.number(), v.minValue(0)),
totalCollateral: v.pipe(v.number(), v.minValue(0)),
enabled: v.optional(v.boolean()),
until: v.optional(v.number()),
until: v.optional(v.pipe(v.number(), v.minValue(0))),
});
export type CodexAvailabilityPatchResponse =
paths["/sales/availability/{id}"]["patch"]["responses"][204]["content"];
export type CodexAvailabilityPatchBody = Exclude<
paths["/sales/availability"]["post"]["requestBody"],
undefined
>["content"]["application/json"];
export type CodexAvailabilityPatchBody = Partial<
Exclude<
paths["/sales/availability"]["post"]["requestBody"],
undefined
>["content"]["application/json"]
>;
export type CodexCreateAvailabilityInput = v.InferOutput<
typeof CodexCreateAvailabilityInput
@ -65,12 +67,12 @@ export type CodexCreateAvailabilityInput = v.InferOutput<
export const CodexAvailabilityPatchInput = v.strictObject({
id: v.string(),
totalSize: v.pipe(v.number(), v.minValue(1)),
duration: v.pipe(v.number(), v.minValue(1)),
minPricePerBytePerSecond: v.number(),
totalCollateral: v.number(),
totalSize: v.optional(v.pipe(v.number(), v.minValue(1))),
duration: v.optional(v.pipe(v.number(), v.minValue(1))),
minPricePerBytePerSecond: v.optional(v.pipe(v.number(), v.minValue(1))),
totalCollateral: v.optional(v.pipe(v.number(), v.minValue(0))),
enabled: v.optional(v.boolean()),
until: v.optional(v.number()),
until: v.optional(v.pipe(v.number(), v.minValue(0))),
});
export type CodexAvailabilityPatchInput = v.InferOutput<
@ -120,12 +122,12 @@ export type CodexStorageRequestCreateBody = Exclude<
export const CodexCreateStorageRequestInput = v.strictObject({
cid: v.string(),
duration: v.pipe(v.number(), v.minValue(1)),
pricePerBytePerSecond: v.number(),
proofProbability: v.number(),
pricePerBytePerSecond: v.pipe(v.number(), v.minValue(1)),
proofProbability: v.pipe(v.number(), v.minValue(1)),
nodes: v.optional(v.number(), 1),
tolerance: v.optional(v.number(), 0),
tolerance: v.optional(v.pipe(v.number(), v.minValue(1)), 1),
expiry: v.pipe(v.number(), v.minValue(1)),
collateralPerByte: v.number(),
collateralPerByte: v.pipe(v.number(), v.minValue(1)),
});
export type CodexCreateStorageRequestInput = v.InferOutput<

36
src/node/node.spec.ts Normal file
View File

@ -0,0 +1,36 @@
import { assert, describe, expect, it, vi } from "vitest";
import { CodexNode } from "./node";
import { Fetch } from "../fetch-safe/fetch-safe";
describe("node", () => {
const clientUrl = process.env["CLIENT_URL"] || "http://localhost:8080";
const node = new CodexNode(clientUrl);
it("gets the json spr", async () => {
const spr = await node.spr("json");
assert.ok(spr.error == false);
assert.ok(spr.data);
});
it("gets the text spr", async () => {
const spr = await node.spr("text");
assert.ok(spr.error == false);
assert.ok(spr.data);
});
it("connects to a peer", async () => {
const spy = vi.spyOn(Fetch, "safeText");
spy.mockImplementationOnce(() =>
Promise.resolve({ error: false, data: "" })
);
await node.connect("1234", ["5678"]);
expect(spy).toHaveBeenCalledWith(
clientUrl + "/api/codex/v1/connect/1234?addrs=5678",
{
headers: {},
method: "GET",
}
);
});
});