import { describe, expect, it, vi } from "vitest" ;
import {
clearDeviceAuthTokenFromStore,
loadDeviceAuthTokenFromStore,
storeDeviceAuthTokenInStore,
type DeviceAuthStoreAdapter,
} from "./device-auth-store.js" ;
function createAdapter(initialStore: ReturnType<DeviceAuthStoreAdapter["readStore" ]> = null ) {
let store = initialStore;
const writes: unknown[] = [];
const adapter: DeviceAuthStoreAdapter = {
readStore: () => store,
writeStore: (next) => {
store = next;
writes.push(next);
},
};
return { adapter, writes, readStore: () => store };
}
describe("device-auth-store" , () => {
it("loads only matching device ids and normalized roles" , () => {
const { adapter } = createAdapter({
version: 1 ,
deviceId: "device-1" ,
tokens: {
operator: {
token: "secret" ,
role: "operator" ,
scopes: ["operator.read" ],
updatedAtMs: 1 ,
},
},
});
expect(
loadDeviceAuthTokenFromStore({
adapter,
deviceId: "device-1" ,
role: " operator " ,
}),
).toMatchObject({ token: "secret" });
expect(
loadDeviceAuthTokenFromStore({
adapter,
deviceId: "device-2" ,
role: "operator" ,
}),
).toBeNull();
});
it("returns null for missing stores and malformed token entries" , () => {
expect(
loadDeviceAuthTokenFromStore({
adapter: createAdapter().adapter,
deviceId: "device-1" ,
role: "operator" ,
}),
).toBeNull();
const { adapter } = createAdapter({
version: 1 ,
deviceId: "device-1" ,
tokens: {
operator: {
token: 123 as unknown as string,
role: "operator" ,
scopes: [],
updatedAtMs: 1 ,
},
},
});
expect(
loadDeviceAuthTokenFromStore({
adapter,
deviceId: "device-1" ,
role: "operator" ,
}),
).toBeNull();
});
it("stores normalized roles and deduped sorted scopes while preserving same-device tokens" , () => {
vi.spyOn(Date, "now" ).mockReturnValue(1234 );
const { adapter, writes, readStore } = createAdapter({
version: 1 ,
deviceId: "device-1" ,
tokens: {
node: {
token: "node-token" ,
role: "node" ,
scopes: ["node.invoke" ],
updatedAtMs: 10 ,
},
},
});
const entry = storeDeviceAuthTokenInStore({
adapter,
deviceId: "device-1" ,
role: " operator " ,
token: "operator-token" ,
scopes: [" operator.write " , "operator.read" , "operator.read" , "" ],
});
expect(entry).toEqual({
token: "operator-token" ,
role: "operator" ,
scopes: ["operator.read" , "operator.write" ],
updatedAtMs: 1234 ,
});
expect(writes).toHaveLength(1 );
expect(readStore()).toEqual({
version: 1 ,
deviceId: "device-1" ,
tokens: {
node: {
token: "node-token" ,
role: "node" ,
scopes: ["node.invoke" ],
updatedAtMs: 10 ,
},
operator: entry,
},
});
});
it("replaces stale stores from other devices instead of merging them" , () => {
const { adapter, readStore } = createAdapter({
version: 1 ,
deviceId: "device-2" ,
tokens: {
operator: {
token: "old-token" ,
role: "operator" ,
scopes: [],
updatedAtMs: 1 ,
},
},
});
storeDeviceAuthTokenInStore({
adapter,
deviceId: "device-1" ,
role: "node" ,
token: "node-token" ,
});
expect(readStore()).toEqual({
version: 1 ,
deviceId: "device-1" ,
tokens: {
node: {
token: "node-token" ,
role: "node" ,
scopes: [],
updatedAtMs: expect.any(Number),
},
},
});
});
it("overwrites existing entries for the same normalized role" , () => {
vi.spyOn(Date, "now" ).mockReturnValue(2222 );
const { adapter, readStore } = createAdapter({
version: 1 ,
deviceId: "device-1" ,
tokens: {
operator: {
token: "old-token" ,
role: "operator" ,
scopes: ["operator.read" ],
updatedAtMs: 10 ,
},
},
});
const entry = storeDeviceAuthTokenInStore({
adapter,
deviceId: "device-1" ,
role: " operator " ,
token: "new-token" ,
scopes: ["operator.write" ],
});
expect(entry).toEqual({
token: "new-token" ,
role: "operator" ,
scopes: ["operator.read" , "operator.write" ],
updatedAtMs: 2222 ,
});
expect(readStore()).toEqual({
version: 1 ,
deviceId: "device-1" ,
tokens: {
operator: entry,
},
});
});
it("avoids writes when clearing missing roles or mismatched devices" , () => {
const missingRole = createAdapter({
version: 1 ,
deviceId: "device-1" ,
tokens: {},
});
clearDeviceAuthTokenFromStore({
adapter: missingRole.adapter,
deviceId: "device-1" ,
role: "operator" ,
});
expect(missingRole.writes).toHaveLength(0 );
const otherDevice = createAdapter({
version: 1 ,
deviceId: "device-2" ,
tokens: {
operator: {
token: "secret" ,
role: "operator" ,
scopes: [],
updatedAtMs: 1 ,
},
},
});
clearDeviceAuthTokenFromStore({
adapter: otherDevice.adapter,
deviceId: "device-1" ,
role: "operator" ,
});
expect(otherDevice.writes).toHaveLength(0 );
});
it("removes normalized roles when clearing stored tokens" , () => {
const { adapter, writes, readStore } = createAdapter({
version: 1 ,
deviceId: "device-1" ,
tokens: {
operator: {
token: "secret" ,
role: "operator" ,
scopes: ["operator.read" ],
updatedAtMs: 1 ,
},
node: {
token: "node-token" ,
role: "node" ,
scopes: [],
updatedAtMs: 2 ,
},
},
});
clearDeviceAuthTokenFromStore({
adapter,
deviceId: "device-1" ,
role: " operator " ,
});
expect(writes).toHaveLength(1 );
expect(readStore()).toEqual({
version: 1 ,
deviceId: "device-1" ,
tokens: {
node: {
token: "node-token" ,
role: "node" ,
scopes: [],
updatedAtMs: 2 ,
},
},
});
});
});
Messung V0.5 in Prozent C=100 H=96 G=97
¤ Dauer der Verarbeitung: 0.12 Sekunden
(vorverarbeitet am 2026-06-10)
¤
*© Formatika GbR, Deutschland