Skip to content
Open
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
44 changes: 37 additions & 7 deletions packages/lib/sdk/src/pluto/Pluto.ts
Original file line number Diff line number Diff line change
Expand Up @@ -677,7 +677,7 @@
});

// ?? this seems presumptuous? couldnt hostDID be re-used?
const link = this.onlyOne(links);
const link = this.onlyOne(links, `getPairByDID(${did.toString()})`);
const didPair = this.mapDIDPairToDomain(link);

return didPair;
Expand All @@ -694,7 +694,7 @@
{
selector: { alias, role: Models.DIDLink.role.pair }
});
const link = this.onlyOne(links);
const link = this.onlyOne(links, `getPairByName(${alias})`);
const didPair = this.mapDIDPairToDomain(link);

return didPair;
Expand Down Expand Up @@ -739,16 +739,24 @@
const mediatorLink = links.find(x => x.hostId === hostId && x.role === Models.DIDLink.role.mediator.valueOf());
const routingLink = links.find(x => x.hostId === hostId && x.role === Models.DIDLink.role.routing.valueOf());

// One of the two expected DID links (mediator or routing) is
// missing for this host — data integrity issue.
if (!mediatorLink || !routingLink) {
throw new Error();
throw new Error(
`Missing mediator or routing DID link for hostId: ${hostId}`
);
}

const hostDID = await this.Repositories.DIDs.byUUID(hostId);
const mediatorDID = await this.Repositories.DIDs.byUUID(mediatorLink.targetId);
const routingDID = await this.Repositories.DIDs.byUUID(routingLink.targetId);

// A DID that should exist (host, mediator, or routing) resolved
// to null — data integrity or ordering issue.
if (!hostDID || !mediatorDID || !routingDID) {
throw new Error();
throw new Error(
`Empty DID for hostId: ${hostId} (host: ${!!hostDID}, mediator: ${!!mediatorDID}, routing: ${!!routingDID})`
);
}

const domain: Domain.Mediator = { hostDID, mediatorDID, routingDID };
Expand Down Expand Up @@ -785,9 +793,31 @@
});
}

private onlyOne<T>(arr: T[]): T {
const item = arr.at(0);
if (!item || arr.length !== 1) throw new Error("something wrong");
/**
* Assert that an array has exactly one element and return it.
*
* Accepts an optional `context` string so callers can identify
* themselves in the error message (e.g. the DID or alias being
* looked up) — makes debugging much faster than a bare
* `"something wrong"`.
*
* Two distinct failure modes are reported:
* - `arr.length !== 1` — reports the actual count
* - `arr[0]` is falsy — reports an unexpected null/empty slot
*/
private onlyOne<T>(arr: T[], context?: string): T {
if (arr.length !== 1) {
throw new Error(
`Expected one result but got ${arr.length}${context ? `: ${context}` : ""}`

Check warning on line 811 in packages/lib/sdk/src/pluto/Pluto.ts

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Refactor this code to not use nested template literals.

See more on https://sonarcloud.io/project/issues?id=hyperledger-identus_sdk-ts&issues=AZ5ZEeTmBnT8UsONE7Fg&open=AZ5ZEeTmBnT8UsONE7Fg&pullRequest=651
);
}

const item = arr[0];
if (!item) {
throw new Error(
`Unexpected empty result${context ? `: ${context}` : ""}`

Check warning on line 818 in packages/lib/sdk/src/pluto/Pluto.ts

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Refactor this code to not use nested template literals.

See more on https://sonarcloud.io/project/issues?id=hyperledger-identus_sdk-ts&issues=AZ5ZF9kmcGJh43cgUPac&open=AZ5ZF9kmcGJh43cgUPac&pullRequest=651
);
}

return item;
}
Expand Down
84 changes: 84 additions & 0 deletions packages/lib/sdk/tests/pluto/Pluto.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -384,6 +384,90 @@ describe("Pluto", () => {
expect(result?.body).deep.equal(message.body);
});

describe("onlyOne error context", () => {
it("should throw with context when getPairByName finds no results", async () => {
await expect(
instance.getPairByName("nonexistent")
).rejects.toThrow(/Expected one result but got 0.*getPairByName/);
});

it("should throw with context when getPairByName finds multiple results", async () => {
const host1 = SDK.Domain.DID.fromString("did:prism:111");
const receiver1 = SDK.Domain.DID.fromString("did:prism:222");
const host2 = SDK.Domain.DID.fromString("did:prism:333");
const receiver2 = SDK.Domain.DID.fromString("did:prism:444");
const name = "duplicate";

const pk = new SDK.Ed25519PrivateKey(
Buffer.from("01011010011101010100011000100010")
);
for (const did of [host1, receiver1, host2, receiver2]) {
await instance.storePrismDID(did, pk);
}

await instance.storeDIDPair(host1, receiver1, name);
await instance.storeDIDPair(host2, receiver2, name);

await expect(
instance.getPairByName(name)
).rejects.toThrow(/Expected one result but got 2.*getPairByName/);
});
});

describe("getAllMediators error handling", () => {
it("should throw when mediator link exists without routing link", async () => {
const hostDID = SDK.Domain.DID.fromString("did:prism:901");
const mediatorDID = SDK.Domain.DID.fromString("did:prism:902");
const pk = new SDK.Ed25519PrivateKey(
Buffer.from("01011010011101010100011000100010")
);
await instance.storePrismDID(hostDID, pk);
await instance.storePrismDID(mediatorDID, pk);

// Insert a mediator link without the corresponding routing link
await (instance.store as any).insert("did-link", {
uuid: randomUUID(),
role: 2,
hostId: hostDID.uuid,
targetId: mediatorDID.uuid,
});

await expect(
instance.getAllMediators()
).rejects.toThrow(/Missing mediator or routing DID link for hostId/);
});

it("should throw when DIDs are missing for stored links", async () => {
const hostDID = SDK.Domain.DID.fromString("did:prism:903");
const mediatorDID = SDK.Domain.DID.fromString("did:prism:904");
const routingDID = SDK.Domain.DID.fromString("did:prism:905");
const pk = new SDK.Ed25519PrivateKey(
Buffer.from("01011010011101010100011000100010")
);
await instance.storePrismDID(hostDID, pk);
await instance.storePrismDID(mediatorDID, pk);
// Intentionally NOT storing routingDID

// Insert both links but routing DID won't resolve
await (instance.store as any).insert("did-link", {
uuid: randomUUID(),
role: 2,
hostId: hostDID.uuid,
targetId: mediatorDID.uuid,
});
await (instance.store as any).insert("did-link", {
uuid: randomUUID(),
role: 3,
hostId: hostDID.uuid,
targetId: routingDID.uuid,
});

await expect(
instance.getAllMediators()
).rejects.toThrow(/Empty DID for hostId/);
});
});

//
it("should get all mediators", async function () {
const mediator = SDK.Domain.DID.fromString("did:prism:123");
Expand Down
Loading