Fix tests

This commit is contained in:
Wolfgang Kulhanek
2025-10-17 10:59:29 +02:00
parent 254bd5a149
commit 593555bc82
2 changed files with 72 additions and 63 deletions

View File

@@ -60,7 +60,7 @@ setInterval(() => {
streamSessions.delete(sid); streamSessions.delete(sid);
} }
} }
}, 5 * 60 * 1000); // Run every 5 minutes }, 5 * 60 * 1000).unref(); // Run every 5 minutes, but don't prevent process exit
interface RangeFilter extends Transform { interface RangeFilter extends Transform {
range: (length: number) => string; range: (length: number) => string;

View File

@@ -462,7 +462,7 @@ function bindSmapiSoapServiceToExpress(
); );
}; };
const swapToken = (expiredToken:string | undefined) => (newToken:SmapiToken) => { const swapToken = (expiredToken:string) => (newToken:SmapiToken) => {
logger.debug("oldToken: "+expiredToken); logger.debug("oldToken: "+expiredToken);
logger.debug("newToken: "+JSON.stringify(newToken)); logger.debug("newToken: "+JSON.stringify(newToken));
sonosSoap.associateCredentialsForToken(newToken.token, newToken, expiredToken); sonosSoap.associateCredentialsForToken(newToken.token, newToken, expiredToken);
@@ -513,12 +513,10 @@ function bindSmapiSoapServiceToExpress(
throw SMAPI_FAULT_LOGIN_UNAUTHORIZED; throw SMAPI_FAULT_LOGIN_UNAUTHORIZED;
}); });
} else if (isExpiredTokenError(authOrFail)) { } else if (isExpiredTokenError(authOrFail)) {
// Get the old SMAPI token from credentials before refreshing
const oldSmapiToken = credentials?.loginToken.token;
throw await pipe( throw await pipe(
musicService.refreshToken(authOrFail.expiredToken), musicService.refreshToken(authOrFail.expiredToken),
TE.map((it) => smapiAuthTokens.issue(it.serviceToken)), TE.map((it) => smapiAuthTokens.issue(it.serviceToken)),
TE.tap(swapToken(oldSmapiToken)), TE.tap(swapToken(authOrFail.expiredToken)),
TE.map((newToken) => ({ TE.map((newToken) => ({
Fault: { Fault: {
faultcode: "Client.TokenRefreshRequired", faultcode: "Client.TokenRefreshRequired",
@@ -566,10 +564,8 @@ function bindSmapiSoapServiceToExpress(
}, },
}), }),
refreshAuthToken: async (_, _2, soapyHeaders: SoapyHeaders, refreshAuthToken: async (_, _2, soapyHeaders: SoapyHeaders,
req: Pick<Request, "headers">) => { { headers }: Pick<Request, "headers">) => {
const creds = useHeaderIfPresent(soapyHeaders?.credentials, req.headers); const creds = useHeaderIfPresent(soapyHeaders?.credentials, headers);
// Get the old SMAPI token from credentials before refreshing
const oldSmapiToken = creds?.loginToken.token;
const serviceToken = pipe( const serviceToken = pipe(
auth(creds), auth(creds),
E.fold( E.fold(
@@ -586,7 +582,7 @@ function bindSmapiSoapServiceToExpress(
return pipe( return pipe(
musicService.refreshToken(serviceToken), musicService.refreshToken(serviceToken),
TE.map((it) => smapiAuthTokens.issue(it.serviceToken)), TE.map((it) => smapiAuthTokens.issue(it.serviceToken)),
TE.tap(swapToken(oldSmapiToken)), // ignores the return value, like a tee or peek TE.tap(swapToken(serviceToken)), // ignores the return value, like a tee or peek
TE.map((it) => ({ TE.map((it) => ({
refreshAuthTokenResult: { refreshAuthTokenResult: {
authToken: it.token, authToken: it.token,
@@ -602,9 +598,9 @@ function bindSmapiSoapServiceToExpress(
{ id }: { id: string }, { id }: { id: string },
_, _,
soapyHeaders: SoapyHeaders, soapyHeaders: SoapyHeaders,
req: Pick<Request, "headers"> { headers }: Pick<Request, "headers">
) => ) => {
login(soapyHeaders?.credentials, req.headers) return login(soapyHeaders?.credentials, headers)
.then(splitId(id)) .then(splitId(id))
.then(({ musicLibrary, credentials, type, typeId }) => { .then(({ musicLibrary, credentials, type, typeId }) => {
switch (type) { switch (type) {
@@ -637,14 +633,15 @@ function bindSmapiSoapServiceToExpress(
default: default:
throw `Unsupported type:${type}`; throw `Unsupported type:${type}`;
} }
}), });
},
getMediaMetadata: async ( getMediaMetadata: async (
{ id }: { id: string }, { id }: { id: string },
_, _,
soapyHeaders: SoapyHeaders, soapyHeaders: SoapyHeaders,
req: Pick<Request, "headers"> { headers }: Pick<Request, "headers">
) => ) => {
login(soapyHeaders?.credentials, req.headers) return login(soapyHeaders?.credentials, headers)
.then(splitId(id)) .then(splitId(id))
.then(async ({ musicLibrary, apiKey, type, typeId }) => { .then(async ({ musicLibrary, apiKey, type, typeId }) => {
switch (type) { switch (type) {
@@ -659,14 +656,15 @@ function bindSmapiSoapServiceToExpress(
default: default:
throw `Unsupported type:${type}`; throw `Unsupported type:${type}`;
} }
}), });
},
search: async ( search: async (
{ id, term }: { id: string; term: string }, { id, term }: { id: string; term: string },
_, _,
soapyHeaders: SoapyHeaders, soapyHeaders: SoapyHeaders,
req: Pick<Request, "headers"> { headers }: Pick<Request, "headers">
) => ) => {
login(soapyHeaders?.credentials, req.headers) return login(soapyHeaders?.credentials, headers)
.then(splitId(id)) .then(splitId(id))
.then(async ({ musicLibrary, apiKey }) => { .then(async ({ musicLibrary, apiKey }) => {
switch (id) { switch (id) {
@@ -700,7 +698,8 @@ function bindSmapiSoapServiceToExpress(
default: default:
throw `Unsupported search by:${id}`; throw `Unsupported search by:${id}`;
} }
}), });
},
getExtendedMetadata: async ( getExtendedMetadata: async (
{ {
id, id,
@@ -710,9 +709,9 @@ function bindSmapiSoapServiceToExpress(
{ id: string; index: number; count: number; recursive: boolean }, { id: string; index: number; count: number; recursive: boolean },
_, _,
soapyHeaders: SoapyHeaders, soapyHeaders: SoapyHeaders,
req: Pick<Request, "headers"> { headers }: Pick<Request, "headers">
) => ) => {
login(soapyHeaders?.credentials, req.headers) return login(soapyHeaders?.credentials, headers)
.then(splitId(id)) .then(splitId(id))
.then(async ({ musicLibrary, apiKey, type, typeId }) => { .then(async ({ musicLibrary, apiKey, type, typeId }) => {
const paging = { _index: index, _count: count }; const paging = { _index: index, _count: count };
@@ -772,7 +771,8 @@ function bindSmapiSoapServiceToExpress(
default: default:
throw `Unsupported getExtendedMetadata id=${id}`; throw `Unsupported getExtendedMetadata id=${id}`;
} }
}), });
},
getMetadata: async ( getMetadata: async (
{ {
id, id,
@@ -782,13 +782,13 @@ function bindSmapiSoapServiceToExpress(
{ id: string; index: number; count: number; recursive: boolean }, { id: string; index: number; count: number; recursive: boolean },
_, _,
soapyHeaders: SoapyHeaders, soapyHeaders: SoapyHeaders,
req: Pick<Request, "headers"> { headers }: Pick<Request, "headers">
) => ) => {
login(soapyHeaders?.credentials, req.headers) const acceptLanguage = headers["accept-language"];
return login(soapyHeaders?.credentials, headers)
.then(splitId(id)) .then(splitId(id))
.then(({ musicLibrary, apiKey, type, typeId }) => { .then(({ musicLibrary, apiKey, type, typeId }) => {
const paging = { _index: index, _count: count }; const paging = { _index: index, _count: count };
const acceptLanguage = req.headers["accept-language"];
logger.debug( logger.debug(
`Fetching metadata type=${type}, typeId=${typeId}, acceptLanguage=${acceptLanguage}` `Fetching metadata type=${type}, typeId=${typeId}, acceptLanguage=${acceptLanguage}`
); );
@@ -1103,14 +1103,15 @@ function bindSmapiSoapServiceToExpress(
default: default:
throw `Unsupported getMetadata id=${id}`; throw `Unsupported getMetadata id=${id}`;
} }
}), });
},
createContainer: async ( createContainer: async (
{ title, seedId }: { title: string; seedId: string | undefined }, { title, seedId }: { title: string; seedId: string | undefined },
_, _,
soapyHeaders: SoapyHeaders, soapyHeaders: SoapyHeaders,
req: Pick<Request, "headers"> { headers }: Pick<Request, "headers">
) => ) => {
login(soapyHeaders?.credentials, req.headers) return login(soapyHeaders?.credentials, headers)
.then(({ musicLibrary }) => .then(({ musicLibrary }) =>
musicLibrary musicLibrary
.createPlaylist(title) .createPlaylist(title)
@@ -1130,35 +1131,38 @@ function bindSmapiSoapServiceToExpress(
id: `playlist:${it.id}`, id: `playlist:${it.id}`,
updateId: "", updateId: "",
}, },
})), }));
},
deleteContainer: async ( deleteContainer: async (
{ id }: { id: string }, { id }: { id: string },
_, _,
soapyHeaders: SoapyHeaders, soapyHeaders: SoapyHeaders,
req: Pick<Request, "headers"> { headers }: Pick<Request, "headers">
) => ) => {
login(soapyHeaders?.credentials, req.headers) return login(soapyHeaders?.credentials, headers)
.then(({ musicLibrary }) => musicLibrary.deletePlaylist(id)) .then(({ musicLibrary }) => musicLibrary.deletePlaylist(id))
.then((_) => ({ deleteContainerResult: {} })), .then((_) => ({ deleteContainerResult: {} }));
},
addToContainer: async ( addToContainer: async (
{ id, parentId }: { id: string; parentId: string }, { id, parentId }: { id: string; parentId: string },
_, _,
soapyHeaders: SoapyHeaders, soapyHeaders: SoapyHeaders,
req: Pick<Request, "headers"> { headers }: Pick<Request, "headers">
) => ) => {
login(soapyHeaders?.credentials, req.headers) return login(soapyHeaders?.credentials, headers)
.then(splitId(id)) .then(splitId(id))
.then(({ musicLibrary, typeId }) => .then(({ musicLibrary, typeId }) =>
musicLibrary.addToPlaylist(parentId.split(":")[1]!, typeId) musicLibrary.addToPlaylist(parentId.split(":")[1]!, typeId)
) )
.then((_) => ({ addToContainerResult: { updateId: "" } })), .then((_) => ({ addToContainerResult: { updateId: "" } }));
},
removeFromContainer: async ( removeFromContainer: async (
{ id, indices }: { id: string; indices: string }, { id, indices }: { id: string; indices: string },
_, _,
soapyHeaders: SoapyHeaders, soapyHeaders: SoapyHeaders,
req: Pick<Request, "headers"> { headers }: Pick<Request, "headers">
) => ) => {
login(soapyHeaders?.credentials, req.headers) return login(soapyHeaders?.credentials, headers)
.then(splitId(id)) .then(splitId(id))
.then((it) => ({ .then((it) => ({
...it, ...it,
@@ -1175,27 +1179,29 @@ function bindSmapiSoapServiceToExpress(
musicLibrary.removeFromPlaylist(typeId, indices); musicLibrary.removeFromPlaylist(typeId, indices);
} }
}) })
.then((_) => ({ removeFromContainerResult: { updateId: "" } })), .then((_) => ({ removeFromContainerResult: { updateId: "" } }));
},
rateItem: async ( rateItem: async (
{ id, rating }: { id: string; rating: number }, { id, rating }: { id: string; rating: number },
_, _,
soapyHeaders: SoapyHeaders, soapyHeaders: SoapyHeaders,
req: Pick<Request, "headers"> { headers }: Pick<Request, "headers">
) => ) => {
login(soapyHeaders?.credentials, req.headers) return login(soapyHeaders?.credentials, headers)
.then(splitId(id)) .then(splitId(id))
.then(({ musicLibrary, typeId }) => .then(({ musicLibrary, typeId }) =>
musicLibrary.rate(typeId, ratingFromInt(Math.abs(rating))) musicLibrary.rate(typeId, ratingFromInt(Math.abs(rating)))
) )
.then((_) => ({ rateItemResult: { shouldSkip: false } })), .then((_) => ({ rateItemResult: { shouldSkip: false } }));
},
setPlayedSeconds: async ( setPlayedSeconds: async (
{ id, seconds }: { id: string; seconds: string }, { id, seconds }: { id: string; seconds: string },
_, _,
soapyHeaders: SoapyHeaders, soapyHeaders: SoapyHeaders,
req: Pick<Request, "headers"> { headers }: Pick<Request, "headers">
) => ) => {
login(soapyHeaders?.credentials, req.headers) return login(soapyHeaders?.credentials, headers)
.then(splitId(id)) .then(splitId(id))
.then(({ musicLibrary, type, typeId }) => { .then(({ musicLibrary, type, typeId }) => {
switch (type) { switch (type) {
@@ -1217,15 +1223,16 @@ function bindSmapiSoapServiceToExpress(
}) })
.then((_) => ({ .then((_) => ({
setPlayedSecondsResult: {}, setPlayedSecondsResult: {},
})), }));
},
reportPlaySeconds: async ( reportPlaySeconds: async (
{ id, seconds }: { id: string; seconds: string }, { id, seconds }: { id: string; seconds: string },
_, _,
soapyHeaders: SoapyHeaders, soapyHeaders: SoapyHeaders,
req: Pick<Request, "headers"> { headers }: Pick<Request, "headers">
) => ) => {
login(soapyHeaders?.credentials, req.headers) return login(soapyHeaders?.credentials, headers)
.then(splitId(id)) .then(splitId(id))
.then(({ type, typeId }) => { .then(({ type, typeId }) => {
if (type === "track") { if (type === "track") {
@@ -1237,15 +1244,16 @@ function bindSmapiSoapServiceToExpress(
}) })
.then((_) => ({ .then((_) => ({
reportPlaySecondsResult: { interval: 30 }, reportPlaySecondsResult: { interval: 30 },
})), }));
},
reportPlayStatus: async ( reportPlayStatus: async (
{ id, status }: { id: string; status: string }, { id, status }: { id: string; status: string },
_, _,
soapyHeaders: SoapyHeaders, soapyHeaders: SoapyHeaders,
req: Pick<Request, "headers"> { headers }: Pick<Request, "headers">
) => ) => {
login(soapyHeaders?.credentials, req.headers) return login(soapyHeaders?.credentials, headers)
.then(splitId(id)) .then(splitId(id))
.then(({ musicLibrary, type, typeId }) => { .then(({ musicLibrary, type, typeId }) => {
if (type === "track") { if (type === "track") {
@@ -1256,7 +1264,8 @@ function bindSmapiSoapServiceToExpress(
} }
return Promise.resolve(true); return Promise.resolve(true);
}) })
.then((_) => ({})), .then((_) => ({}));
},
}, },
}, },
}, },