Skip to content

Commit

Permalink
Merge pull request #86 from timoschlueter/feature/add-tests
Browse files Browse the repository at this point in the history
Feature: Add tests
  • Loading branch information
timoschlueter authored Dec 30, 2022
2 parents 1146f83 + fc44577 commit 03ec1bc
Show file tree
Hide file tree
Showing 15 changed files with 9,252 additions and 1,536 deletions.
7 changes: 6 additions & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
FROM node:18
LABEL version="2.0.1"
LABEL description="Script written in TypeScript that uploads CGM readings from LibreLink Up to Nightscout"

# Create app directory
Expand All @@ -13,4 +12,10 @@ RUN npm install
# Bundle app source
COPY . /usr/src/app

# Run tests
RUN npm run test

RUN rm -r tests
RUN rm -r coverage

CMD [ "npm", "start" ]
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
# Nightscout LibreLink Up Uploader/Sidecar

![docker-image](https://github.com/timoschlueter/nightscout-librelink-up/actions/workflows/docker-image.yml/badge.svg)

Script written in TypeScript that uploads CGM readings from LibreLink Up to Nightscout. The upload should
work with at least Freestyle Libre 2 (FGM) and Libre 3 CGM sensors.

Expand Down
5 changes: 5 additions & 0 deletions jest.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
module.exports = {
preset: "ts-jest",
testEnvironment: "node",
setupFilesAfterEnv: ["./tests/setup.js"]
};
8,555 changes: 7,038 additions & 1,517 deletions package-lock.json

Large diffs are not rendered by default.

9 changes: 7 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "nightscout-librelink-up",
"version": "2.0.1",
"version": "2.1.0",
"description": "Script written in TypeScript that uploads CGM readings from LibreLink Up to Nightscout",
"main": "dist/index.js",
"scripts": {
Expand All @@ -9,7 +9,8 @@
"start-dev": "npx ts-node src/index.ts",
"start-heroku": "node dist/index.js",
"lint": "npx eslint . --fix",
"test": "echo \"Error: no test specified\" && exit 1"
"test": "jest --coverage --forceExit",
"test:watch": "jest --watch"
},
"repository": {
"type": "git",
Expand All @@ -35,16 +36,20 @@
"homepage": "https://github.com/timoschlueter/nightscout-librelink-up#readme",
"dependencies": {
"axios": "^1.2.2",
"axios-mock-adapter": "^1.21.2",
"node-cron": "3.0.2",
"winston": "^3.8.2"
},
"devDependencies": {
"@tsconfig/node18": "^1.0.1",
"@types/jest": "^29.2.4",
"@types/node": "^18.11.18",
"@types/node-cron": "^3.0.7",
"@typescript-eslint/eslint-plugin": "^5.47.1",
"@typescript-eslint/parser": "^5.47.1",
"eslint": "^8.30.0",
"jest": "^29.3.1",
"ts-jest": "^29.0.3",
"ts-node": "^10.9.1",
"typescript": "^4.9.4"
}
Expand Down
50 changes: 35 additions & 15 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,6 @@ const USER_AGENT = "FreeStyle LibreLink Up NightScout Uploader";
*/
const LINK_UP_USERNAME = process.env.LINK_UP_USERNAME;
const LINK_UP_PASSWORD = process.env.LINK_UP_PASSWORD;
const LINK_UP_CONNECTION = process.env.LINK_UP_CONNECTION;

/**
* LibreLink Up API Settings (Don't change this unless you know what you are doing)
Expand Down Expand Up @@ -134,12 +133,26 @@ async function main(): Promise<void>
{
logger.info("renew token");
deleteAuthTicket();
await login();
const authTicket: AuthTicket | null = await login();
if (!authTicket)
{
deleteAuthTicket();
return;
}
updateAuthTicket(authTicket);
}
await getGlucoseMeasurements();

const glucoseGraphData: GraphData | null = await getGlucoseMeasurements();

if (!glucoseGraphData)
{
return;
}

await uploadToNightScout(glucoseGraphData);
}

async function login(): Promise<void>
export async function login(): Promise<AuthTicket | null>
{
try
{
Expand All @@ -157,26 +170,27 @@ async function login(): Promise<void>
try
{
logger.info("Logged in to LibreLink Up");
updateAuthTicket(response.data.data.authTicket);
return response.data.data.authTicket;
} catch (err)
{
logger.error("Invalid authentication token. Please check your LibreLink Up credentials", err);
return null;
}
} catch (error)
{
logger.error("Invalid credentials", error);
deleteAuthTicket();
return null;
}
}

async function getGlucoseMeasurements(): Promise<void>
export async function getGlucoseMeasurements(): Promise<GraphData | null>
{
try
{
const connectionId = await getLibreLinkUpConnection();
if (!connectionId)
{
return;
return null;
}

const url = "https://" + LIBRE_LINK_UP_URL + "/llu/connections/" + connectionId + "/graph"
Expand All @@ -186,15 +200,16 @@ async function getGlucoseMeasurements(): Promise<void>
headers: getLluAuthHeaders()
});

await uploadToNightScout(response.data.data);
return response.data.data;
} catch (error)
{
logger.error("Error getting glucose measurements", error);
deleteAuthTicket();
return null;
}
}

async function getLibreLinkUpConnection(): Promise<string | null>
export async function getLibreLinkUpConnection(): Promise<string | null>
{
try
{
Expand Down Expand Up @@ -222,14 +237,14 @@ async function getLibreLinkUpConnection(): Promise<string | null>

dumpConnectionData(connectionData);

if (!LINK_UP_CONNECTION)
if (!process.env.LINK_UP_CONNECTION)
{
logger.warn("You did not specify a Patient-ID in the LINK_UP_CONNECTION environment variable.");
logPickedUpConnection(connectionData[0]);
return connectionData[0].patientId;
}

const connection = connectionData.filter(connectionEntry => connectionEntry.patientId === LINK_UP_CONNECTION)[0];
const connection = connectionData.filter(connectionEntry => connectionEntry.patientId === process.env.LINK_UP_CONNECTION)[0];
if (!connection)
{
logger.error("The specified Patient-ID was not found.");
Expand Down Expand Up @@ -262,13 +277,12 @@ async function lastEntryDate(): Promise<Date | null>
return new Date(response.data.pop().dateString);
}

async function uploadToNightScout(measurementData: GraphData): Promise<void>
export async function createFormattedMeasurements(measurementData: GraphData): Promise<Entry[]>
{
const formattedMeasurements: Entry[] = [];
const glucoseMeasurement = measurementData.connection.glucoseMeasurement;
const measurementDate = getUtcDateFromString(glucoseMeasurement.FactoryTimestamp);

const lastEntry = await lastEntryDate();
const formattedMeasurements: Entry[] = [];

// Add the most recent measurement first
if (lastEntry === null || measurementDate > lastEntry)
Expand All @@ -295,6 +309,12 @@ async function uploadToNightScout(measurementData: GraphData): Promise<void>
});
}
});
return formattedMeasurements;
}

async function uploadToNightScout(measurementData: GraphData): Promise<void>
{
const formattedMeasurements: Entry[] = await createFormattedMeasurements(measurementData);

if (formattedMeasurements.length > 0)
{
Expand Down
192 changes: 192 additions & 0 deletions tests/data/connections.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,192 @@
{
"status": 0,
"data": [
{
"id": "0714c937-0bc2-4be4-9e93-ddfe736b2b1f",
"patientId": "7ad66b40-ba9b-401e-9845-4f49f998cf16",
"country": "DE",
"status": 2,
"firstName": "John",
"lastName": "Doe",
"targetLow": 70,
"targetHigh": 160,
"uom": 1,
"sensor": {
"deviceId": "",
"sn": "ABCDEF1234",
"a": 1671955478,
"w": 60,
"pt": 4,
"s": true,
"lj": false
},
"alarmRules": {
"c": true,
"h": {
"th": 220,
"thmm": 12.2,
"d": 1440,
"f": 0.1
},
"f": {
"th": 55,
"thmm": 3,
"d": 30,
"tl": 10,
"tlmm": 0.6
},
"l": {
"th": 60,
"thmm": 3.3,
"d": 1440,
"tl": 10,
"tlmm": 0.6
},
"nd": {
"i": 20,
"r": 5,
"l": 6
},
"p": 5,
"r": 5,
"std": {}
},
"glucoseMeasurement": {
"FactoryTimestamp": "12/30/2022 4:47:40 PM",
"Timestamp": "12/30/2022 5:47:40 PM",
"type": 1,
"ValueInMgPerDl": 115,
"TrendArrow": 3,
"TrendMessage": null,
"MeasurementColor": 1,
"GlucoseUnits": 1,
"Value": 115,
"isHigh": false,
"isLow": false
},
"glucoseItem": {
"FactoryTimestamp": "12/30/2022 4:47:40 PM",
"Timestamp": "12/30/2022 5:47:40 PM",
"type": 1,
"ValueInMgPerDl": 115,
"TrendArrow": 3,
"TrendMessage": null,
"MeasurementColor": 1,
"GlucoseUnits": 1,
"Value": 115,
"isHigh": false,
"isLow": false
},
"glucoseAlarm": null,
"patientDevice": {
"did": "fbc207fe-27e5-4dec-b09b-c60d18167144",
"dtid": 40068,
"v": "3.4.1.7374",
"ll": 60,
"hl": 220,
"u": 1648155522,
"fixedLowAlarmValues": {
"mgdl": 60,
"mmoll": 3.3
},
"alarms": false,
"fixedLowThreshold": 0
},
"created": 1638703030
},
{
"id": "2f5f5f40-9899-11ec-bed5-0242ac110009",
"patientId": "77179667-ba4b-11eb-ad1f-0242ac110004",
"country": "DE",
"status": 2,
"firstName": "Jane",
"lastName": "Doe",
"targetLow": 70,
"targetHigh": 180,
"uom": 1,
"sensor": null,
"alarmRules": {
"c": true,
"h": {
"on": true,
"th": 210,
"thmm": 11.7,
"d": 1440,
"f": 0.1
},
"f": {
"th": 55,
"thmm": 3,
"d": 30,
"tl": 10,
"tlmm": 0.6
},
"l": {
"on": true,
"th": 70,
"thmm": 3.9,
"d": 1440,
"tl": 10,
"tlmm": 0.6
},
"nd": {
"i": 20,
"r": 5,
"l": 6
},
"p": 5,
"r": 5,
"std": {}
},
"glucoseMeasurement": {
"FactoryTimestamp": "3/13/2022 6:53:10 PM",
"Timestamp": "3/13/2022 7:53:10 PM",
"type": 1,
"ValueInMgPerDl": 103,
"TrendArrow": 3,
"TrendMessage": null,
"MeasurementColor": 1,
"GlucoseUnits": 1,
"Value": 103,
"isHigh": false,
"isLow": false
},
"glucoseItem": {
"FactoryTimestamp": "3/13/2022 6:53:10 PM",
"Timestamp": "3/13/2022 7:53:10 PM",
"type": 1,
"ValueInMgPerDl": 103,
"TrendArrow": 3,
"TrendMessage": null,
"MeasurementColor": 1,
"GlucoseUnits": 1,
"Value": 103,
"isHigh": false,
"isLow": false
},
"glucoseAlarm": null,
"patientDevice": {
"did": "b0f390e4-dc0f-45b4-bde3-5fd4be6f4051",
"dtid": 40068,
"v": "3.2.1.6005",
"l": true,
"ll": 70,
"h": true,
"hl": 210,
"u": 1633435443,
"fixedLowAlarmValues": {
"mgdl": 60,
"mmoll": 3.3
},
"alarms": false,
"fixedLowThreshold": 0
},
"created": 1646054423
}
],
"ticket": {
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjdhZDY2YjQwLWJhOWItNDAxZS05ODQ1LTRmNDlmOTk4Y2YxNiIsImZpcnN0TmFtZSI6IkpvaG4iLCJsYXN0TmFtZSI6IkRvZSIsImNvdW50cnkiOiJERSIsInJlZ2lvbiI6ImRlIiwicm9sZSI6InBhdGllbnQiLCJ1bml0cyI6MSwicHJhY3RpY2VzIjpbXSwiYyI6MSwicyI6ImxsdS5pb3MiLCJleHAiOjE2ODc5NzA4ODl9.km_-IfDlE6sFpEj9VdsVFWhYdVg-EI0umvwPH9Bhu68",
"expires": 1687970889,
"duration": 15552000000
}
}
Loading

0 comments on commit 03ec1bc

Please sign in to comment.