mirror of
https://github.com/logos-storage/logos-storage-js.git
synced 2026-01-02 13:33:07 +00:00
Improve tests
This commit is contained in:
parent
d5ff634879
commit
5f1227fce3
9
.github/workflows/ci.yaml
vendored
9
.github/workflows/ci.yaml
vendored
@ -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
124
src/data/data.spec.ts
Normal 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");
|
||||
});
|
||||
});
|
||||
@ -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");
|
||||
});
|
||||
});
|
||||
|
||||
@ -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")
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
|
||||
@ -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
36
src/node/node.spec.ts
Normal 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",
|
||||
}
|
||||
);
|
||||
});
|
||||
});
|
||||
Loading…
x
Reference in New Issue
Block a user