Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
65 changes: 65 additions & 0 deletions apps/desktop/src/backend/DesktopNetworkInterfaces.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import { assert, describe, it } from "@effect/vitest";
import { HostProcessPlatform } from "@t3tools/shared/hostProcess";
import * as Cause from "effect/Cause";
import * as Effect from "effect/Effect";
import * as Layer from "effect/Layer";
import { beforeEach, vi } from "vite-plus/test";

const { networkInterfacesMock } = vi.hoisted(() => ({
networkInterfacesMock: vi.fn(),
}));

vi.mock("node:os", () => ({
networkInterfaces: networkInterfacesMock,
}));

import * as DesktopNetworkInterfaces from "./DesktopNetworkInterfaces.ts";

const TestLayer = DesktopNetworkInterfaces.layer.pipe(
Layer.provide(Layer.succeed(HostProcessPlatform, "linux")),
);

describe("DesktopNetworkInterfaces", () => {
beforeEach(() => {
networkInterfacesMock.mockReset();
});

it.effect("reads network interfaces through the service", () => {
const interfaces = {
en0: [
{
address: "192.168.1.10",
family: "IPv4",
internal: false,
},
],
};
networkInterfacesMock.mockReturnValueOnce(interfaces);

return Effect.gen(function* () {
const service = yield* DesktopNetworkInterfaces.DesktopNetworkInterfaces;
assert.strictEqual(yield* service.read, interfaces);
}).pipe(Effect.provide(TestLayer));
});

it.effect("preserves network interface read failures as structured defects", () => {
const cause = new Error("network interface probe failed");
networkInterfacesMock.mockImplementationOnce(() => {
throw cause;
});

return Effect.gen(function* () {
const service = yield* DesktopNetworkInterfaces.DesktopNetworkInterfaces;
const exit = yield* Effect.exit(service.read);

assert.equal(exit._tag, "Failure");
if (exit._tag === "Failure") {
const error = Cause.squash(exit.cause);
assert.instanceOf(error, DesktopNetworkInterfaces.DesktopNetworkInterfacesReadError);
assert.equal(error.platform, "linux");
assert.strictEqual(error.cause, cause);
assert.equal(error.message, "Failed to read desktop network interfaces on linux.");
}
}).pipe(Effect.provide(TestLayer));
});
});
27 changes: 23 additions & 4 deletions apps/desktop/src/backend/DesktopNetworkInterfaces.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import * as NodeOS from "node:os";

import { HostProcessPlatform } from "@t3tools/shared/hostProcess";
import * as Context from "effect/Context";
import * as Effect from "effect/Effect";
import * as Layer from "effect/Layer";
import * as Schema from "effect/Schema";

export interface DesktopNetworkInterfaceInfo {
readonly address: string;
Expand All @@ -18,16 +20,33 @@ export type NetworkInterfaces = Readonly<
Record<string, readonly DesktopNetworkInterfaceInfo[] | undefined>
>;

export class DesktopNetworkInterfacesReadError extends Schema.TaggedErrorClass<DesktopNetworkInterfacesReadError>()(
"DesktopNetworkInterfacesReadError",
{
platform: Schema.String,
cause: Schema.Defect(),
},
) {
override get message(): string {
return `Failed to read desktop network interfaces on ${this.platform}.`;
}
}

export class DesktopNetworkInterfaces extends Context.Service<
DesktopNetworkInterfaces,
{
readonly read: Effect.Effect<NetworkInterfaces>;
}
>()("@t3tools/desktop/backend/DesktopNetworkInterfaces") {}

export const make = (): DesktopNetworkInterfaces["Service"] =>
DesktopNetworkInterfaces.of({
read: Effect.sync(() => NodeOS.networkInterfaces()),
export const make = Effect.gen(function* () {
const platform = yield* HostProcessPlatform;
return DesktopNetworkInterfaces.of({
read: Effect.try({
try: () => NodeOS.networkInterfaces(),
catch: (cause) => new DesktopNetworkInterfacesReadError({ platform, cause }),
}).pipe(Effect.orDie),
});
});

export const layer = Layer.succeed(DesktopNetworkInterfaces, make());
export const layer = Layer.effect(DesktopNetworkInterfaces, make);
Loading