Browse Source
commitmasterd6f93b022d
Author: Craig Oates <craig@craigoates.net> Date: Fri Jul 2 17:36:52 2021 +0100 add tests which validate the /status/latest call. The URL which returns the latest status for all the devices at once is validated. The tests are not extensive. They just check to make sure all the intended device are present and the last device on the list is 'device 6'. If any of the device names change or 'device 6' is not the last device, it means the API has changed. commit75d4748150
Author: Craig Oates <craig@craigoates.net> Date: Fri Jul 2 16:51:59 2021 +0100 rename light meter status tests (factory devices). Rename the tests to make it easier to read and make the naming convention align with the status tests for the gallery devices. commit4d8a1e6f9b
Author: Craig Oates <craig@craigoates.net> Date: Fri Jul 2 16:43:47 2021 +0100 correct 'reading' tests to 'status' tests and add new tests. I managed to not notice I wrote tests which check the status (on/off) of the devices but labelled the test as 'reading data' checks. I've relabelled the tests to avoid the confusion. I've added additional test which validate the data produced by the devices in the gallery (part of the Return to Ritherdon project). And, I've written tests which actually validate the light reading data produced by the devices in the welding booths in Ritherdon (again, part of the Return to Ritherdon project). The new tests include code from the Ratify package. I've only used the datetime-p function for now. It checks if the timestamps produced are valid datetime strings -- and can be parsed as such. commitc9559f0a67
Author: Craig Oates <craig@craigoates.net> Date: Fri Jul 2 16:41:18 2021 +0100 add Ratify to the testing system. At the moment, this is test the timestamps returned from the API. If Ratify can parse the timestamps, it returns them. If it can't parse them, it returns NIL. commit2ebd6d2a0f
Author: Craig Oates <craig@craigoates.net> Date: Fri Jul 2 00:45:36 2021 +0100 writing tests for validating light reading data. These tests check the shape of the data produced by the light meters in the welding in booths in Ritherdon. I've place a note with the factory3 tests explaining the reason why there is a factory3 in the system but there is not actual light meter in the third welding booth in Ritherdon. commit77952ad294
Author: Craig Oates <craig@craigoates.net> Date: Fri Jul 2 00:40:35 2021 +0100 ASDF package definition changes. The changes here are done out of frustration and desperation. SLIME was in a right mess (caching issue I assume) so I started changing things in here to try and fix it. This was before I stumbled on the 'restart Emacs/SLIIME' solution. Becuase it's now working, I've just kept the changes in place. The relief of getting 'asdf:test-system' working after about two hours of faffing was enough for me to not touch this file again. I might come back to this at a later date -- see if/how it needs cleaning up. I might just skip it and start a new project -- this is a learning exercise after all. commitd11119bfdc
Author: Craig Oates <craig@craigoates.net> Date: Fri Jul 2 00:37:31 2021 +0100 export additional test function (at package level). This is so you can run the tests with ASDF, I think. It was something I did whilst SLIME was a bit messes-up and I was a bit desperate with trying to fix it. So, I don't know if this additional exported function is needed. Everything seems to work with it still there -- after I restarted Emacs and SLIME -- so I'm keeping it like this for now. commitb055b97e2a
Author: Craig Oates <craig@craigoates.net> Date: Fri Jul 2 00:32:52 2021 +0100 rename tests file to main.lisp. This was not needed in the end. I was having trouble with SLIME and I was getting desperate at one point and starting changing everything. It turns out I just needed to restart SLIME. At the time of writing, I didn't know this or how to do that so I only noticed when I restarted Emacs. It looks like stuff got messed-up in the cache and SLIME couldn't find files/packages because it was looking for old one which were either renames or deleted (due to me frantically changing things). commit8c87cf5b37
Author: Craig Oates <craig@craigoates.net> Date: Wed Jun 30 01:44:45 2021 +0100 fix typo in .gitignore (.directory) Git is picking up my systems .directory files. They are not part of this project's code base. I accidently put /.directory in a previous commit instead of .directory. commit09dd5f9b6e
Author: Craig Oates <craig@craigoates.net> Date: Wed Jun 30 01:40:49 2021 +0100 add functions and comments for making HTTP-Requests. These are comments and functions to get me into the swing of writting Common Lisp. The code here is the main part of the system but this commit is mostly about how this file and its code fits into the bigger picture which is the system/code base. commite912fdc73d
Author: Craig Oates <craig@craigoates.net> Date: Wed Jun 30 01:38:36 2021 +0100 add drakma and cl-json packages to project. These packages are for making an HTTP-Request (to Ritherdon REST API) and parse the JSON returned. commit05b876bea7
Author: Craig Oates <craig@craigoates.net> Date: Tue Jun 29 21:38:48 2021 +0100 add 'hello' function (for quick test after loading package). This is so I can make sure I know ritherdon-rest.lisp can/is loaded correctly -- via Quicklisp or ASDF -- and is ready to start adding the actual code. commit8f8cd147ce
Author: Craig Oates <craig@craigoates.net> Date: Tue Jun 29 21:34:14 2021 +0100 integrate FiveAM into test package def. and main testing file. This builds on the initial set-up in the .asd file. With the .asd file knowing the tests package needs FiveAM, the code here integrates the testing framework in to the .lisp files responsible for housing the tests. The code here is placeholder tests and should be deleted the more I get into the project. They exist just to make sure everything is set-up properly between the various definition/set-up files. commit943a912d96
Author: Craig Oates <craig@craigoates.net> Date: Tue Jun 29 21:32:11 2021 +0100 connect fiveAM testing package to main project in .asd file. This is so you can use asdf:test-system by just calling the ritherdon-rest project -- making it easier to work with. commit26b025361d
Author: Craig Oates <craig@craigoates.net> Date: Tue Jun 29 21:31:11 2021 +0100 remove doc folder. This is beyond where I'm at right now so going to leave it for a future project. commitaa1abec7fe
Author: Craig Oates <craig@craigoates.net> Date: Tue Jun 29 00:22:44 2021 +0100 add fiveAM package and create initial test. I've changed how the tests and doc systems are defined in the .asd file. The changes are based on what SLIME outputted when compiling the project. The initial test are irrelevant to the project. I wrote it to make sure fiveAM (and the test project as a whole) was connected together properly. This test will (should) not remain once the main code is up and running. commit00ba38f3b0
Author: Craig Oates <craig@craigoates.net> Date: Mon Jun 28 23:11:46 2021 +0100 create project files.
Craig Oates
3 years ago
8 changed files with 346 additions and 3 deletions
@ -1,3 +1,3 @@
|
||||
# ritherdon-rest |
||||
# Ritherdon REST |
||||
|
||||
A CLI program which grabs data from the Return to Ritherdon project's REST API server. |
||||
A CLI program which grabs data from the Return to Ritherdon project's REST API server. |
||||
|
@ -0,0 +1,34 @@
|
||||
;;;; ritherdon-rest.asd |
||||
|
||||
(asdf:defsystem #:ritherdon-rest |
||||
:description "Grabs data from the Return to Ritherdon project REST |
||||
API and prints out the results." |
||||
:author "craig@craigoates.net" |
||||
:license "MIT" |
||||
:version "0.0.1" |
||||
:serial t |
||||
:depends-on (:drakma |
||||
:cl-json) |
||||
:pathname "src/" |
||||
:components ((:file "package") |
||||
(:file "ritherdon-rest")) |
||||
:in-order-to ((test-op (test-op :ritherdon-rest/tests)))) |
||||
|
||||
;; The use of '...rest/tests' was because of a warning when trying to |
||||
;; run the code in SLIME. |
||||
(asdf:defsystem #:ritherdon-rest/tests |
||||
:description "The test suite for the ritherdon-rest project." |
||||
:author "craig@craigoates.net" |
||||
:license "MIT" |
||||
:version "0.0.1" |
||||
:serial t |
||||
:depends-on (:ritherdon-rest |
||||
:fiveam |
||||
:ratify) |
||||
:pathname "tests/" |
||||
:components ((:file "package") |
||||
(:file "main")) |
||||
:perform (test-op (o s) |
||||
(uiop:symbol-call :ritherdon-rest-tests :test-quasi))) |
||||
|
||||
|
@ -0,0 +1,7 @@
|
||||
;;;; package.lisp |
||||
|
||||
(defpackage #:ritherdon-rest |
||||
(:use #:cl #:drakma :cl-json) |
||||
(:export :parse-request |
||||
:decode-json)) ; Part of cl-json, getting errors without |
||||
; it in SLIME. |
@ -0,0 +1,37 @@
|
||||
;;;; ritherdon-rest.lisp |
||||
|
||||
;;; A collection of functions to aid in the make |
||||
;;; http-requests to the Ritherdon REST API. This project is help me |
||||
;;; learn Common Lisp. So, there is not much emphasis on being too |
||||
;;; strict on best practices and defensive coding. |
||||
|
||||
;;; NOTE: The Ritherdon REST API is part of an art exhibition at time |
||||
;;; of writing (29/06/2021). You will need to check if the server is |
||||
;;; still running if you want to run this code. |
||||
|
||||
(in-package #:ritherdon-rest) |
||||
|
||||
;; All the data provided by the Ritherdon REST API is in JSON. Becuase |
||||
;; of this I can set the `TEXT-CONTENT-TYPE' to JSON and keep it like |
||||
;; that until further notice. |
||||
(setq drakma:*text-content-types* (cons '("application" . "json") |
||||
drakma:*text-content-types*)) |
||||
|
||||
;; The Base URL which you build all your HTTP Requests on. You will |
||||
;; need to refer back to http://ritherdon.abbether.net/api/ui/ for the |
||||
;; full set of URL's. |
||||
(defvar *base-url* "http://ritherdon.abbether.net/api") |
||||
|
||||
;; The http-request is actually 'drakma:http-request'. I've done it |
||||
;; this way to highlight the feature. It is the same as what you've |
||||
;; used in the past with C# and 'using static' statements. So, not |
||||
;; much to go into here apart from highlighting it's a thing in Common |
||||
;; Lisp, too. |
||||
(defun make-request (query) |
||||
(http-request (concatenate 'string *base-url* query))) |
||||
|
||||
;; Same as the drakma example above. It can read as |
||||
;; 'cl-json:decode-json-from-string' but omitted the 'cl-json' |
||||
;; bit. You will find all package declarations in /src/package.lisp'. |
||||
(defun parse-request (query) |
||||
(decode-json-from-string(make-request query))) |
@ -0,0 +1,255 @@
|
||||
;;;; tests/main.lisp |
||||
|
||||
(in-package #:ritherdon-rest-tests) |
||||
|
||||
(def-suite all-tests |
||||
:description "The master suite of all ritherdon-rest tests.") |
||||
|
||||
(in-suite all-tests) |
||||
|
||||
;;; HAD TO REVERT BACK TO FULL PACKAGE NAMES |
||||
;;; ======================================== |
||||
|
||||
;;; BELOW EXPLAINS HOW YOU CAN OMIT PACKAGE NAMES FROM FUNCTION |
||||
;;; CALLS. BUT, I'VE ADDED THE 'RATIFY' PACKAGE SINCE THEN AND I WAS |
||||
;;; GETTING NAMING CONFLICTS (WITH 'TEST'). SO, I'VE HAD TO USE THE |
||||
;;; FULL 'FIVEAM:TEST' FUNCTION CALLS. I'VE KEPT THE NOT FOR FUTURE |
||||
;;; REFERENCE AND ADDED THIS AS EXTRA CONTEXT FOR WHEN/HOW TO OMIT |
||||
;;; PACKAGE NAMES. |
||||
|
||||
;;; THIS BIT KINDA OUT-OF-DATE... |
||||
|
||||
;;; These two examples show the 'full' call to the fiveAm test |
||||
;;; functions. This is just for reference. The 'namespace' is already |
||||
;;; 'imported' in 'package.lisp'. |
||||
|
||||
;;; (fiveam:test sum-1 |
||||
;;; (fiveam:is (= 3 (+ 1 2)))) |
||||
|
||||
;;; (fiveam:run!) |
||||
|
||||
;;; How you would normally create the tests -- with fiveAM already |
||||
;;; set-up in 'package.lisp' and not needing to be explicit about it |
||||
;;; here. This is similar to 'using static' in C#. |
||||
|
||||
(defun test-quasi() |
||||
(run! 'all-tests)) |
||||
|
||||
;;; REST API Used Here is Temporary |
||||
;;; =============================== |
||||
;;; This REST API is a temporary one -- it's part of an artwork for |
||||
;;; the Return to Ritherdon project. The intention is to retire the |
||||
;;; API when the exhibition ends. So, these tests might fail because |
||||
;;; the API is no longer active. |
||||
|
||||
|
||||
;;; DEVICE STATUS TESTS |
||||
;;; =================== |
||||
;;; These tests are checking the 'shape' of the data returned by the |
||||
;;; HTTP (REST) request matches what is expected. These tests are |
||||
;;; expecting JSON data which looks similar to: |
||||
|
||||
;;; ((:ID . 79) (:STATUS . "off") (:TIME . "2021-07-01T16:00:001")). |
||||
|
||||
;;; ID: The row Id. in the database. |
||||
;;; STATUS: The current power state of the device (I.E. on or off). |
||||
;;; TIME: The timestamp when the status was recorded. |
||||
|
||||
;;; Usually, these updates are logged when each device is either just |
||||
;;; finished powering up or as they are about to power down. |
||||
|
||||
(fiveam:test factory-1-status-data |
||||
:description "Validates the (status) data produced by `FACTORY1'." |
||||
(let ((data (ritherdon-rest:parse-request "/status/latest/1"))) |
||||
;; Data should look something like: |
||||
;; ((:ID . 79) (:STATUS . "off") (:TIME . "2021-07-01T16:00:001")). |
||||
(is (= 3 (length data))) |
||||
(is (equal t (consp data))) |
||||
(is (equal ':id (first (nth 0 data)))) |
||||
(is (equal ':status (first (nth 1 data)))) |
||||
(is (equal ':time (first (nth 2 data)))) |
||||
(is (equal t (typep (cdr (car data)) 'integer))) ; row :id number |
||||
(is (> (cdr (car data)) 0)) |
||||
(is (equal t (typep (cdr (car (cdr data))) 'string))) ; :status value |
||||
(is (equal t (or (string-equal (cdr (car (cdr data))) "off") |
||||
(string-equal (cdr (car (cdr data))) "on")))))) |
||||
|
||||
(fiveam:test factory-2-status-data |
||||
:description "Validates the (status) data produced by `FACTORY2'." |
||||
(let ((data (ritherdon-rest:parse-request "/status/latest/2"))) |
||||
;; Data should look something like: |
||||
;; ((:ID . 79) (:STATUS . "off") (:TIME . "2021-07-01T16:00:001")). |
||||
(is (= 3 (length data))) |
||||
(is (equal t (consp data))) |
||||
(is (equal ':id (first (nth 0 data)))) |
||||
(is (equal ':status (first (nth 1 data)))) |
||||
(is (equal ':time (first (nth 2 data)))) |
||||
(is (equal t (typep (cdr (car data)) 'integer))) ; row :id number |
||||
(is (> (cdr (car data)) 0)) |
||||
(is (equal t (typep (cdr (car (cdr data))) 'string))) ; :status value |
||||
(is (equal t (or (string-equal (cdr (car (cdr data))) "off") |
||||
(string-equal (cdr (car (cdr data))) "on")))))) |
||||
|
||||
(fiveam:test factory-3-status-data |
||||
:description "Validates the (status) data produced by `FACTORY3'." |
||||
(let ((data (ritherdon-rest:parse-request "/status/latest/3"))) |
||||
;; Ritherdon has a third welding booth but it was not included in |
||||
;; the art project so no light sensor was installed. Therefore, |
||||
;; this should always return the 'default/initial/seed' data |
||||
;; readings. |
||||
;; Data should still take the same shape as factory1 and factory 2: |
||||
;; ((:ID . 1) (:STATUS . "off") (:TIME . "2021-04-26T20:42:19.400868")). |
||||
(is (= 3 (length data))) |
||||
(is (equal t (consp data))) |
||||
(is (equal ':id (first (nth 0 data)))) |
||||
(is (equal ':status (first (nth 1 data)))) |
||||
(is (equal ':time (first (nth 2 data)))) |
||||
(is (equal t (typep (cdr (car data)) 'integer))) ; row :id number |
||||
(is (> (cdr (car data)) 0)) |
||||
(is (equal t (typep (cdr (car (cdr data))) 'string))) ; :status value |
||||
(is (equal t (or (string-equal (cdr (car (cdr data))) "off") |
||||
(string-equal (cdr (car (cdr data))) "on")))))) |
||||
|
||||
(fiveam:test gallery-1-status-data |
||||
:description "Validates the (status) data produced by `GALLERY1'." |
||||
;; Data should still take the same shape as others: |
||||
;; ((:ID . 1) (:STATUS . "off") (:TIME . "2021-04-26T20:42:19.400868")) |
||||
(let ((data (ritherdon-rest:parse-request "/status/latest/4"))) |
||||
(is (= 3 (length data))) |
||||
(is (equal t (consp data))) |
||||
(is (equal ':id (first (nth 0 data)))) |
||||
(is (equal ':status (first (nth 1 data)))) |
||||
(is (equal ':time (first (nth 2 data)))) |
||||
(is (equal t (typep (cdr (car data)) 'integer))) ; row :id number |
||||
(is (> (cdr (car data)) 0)) |
||||
(is (equal t (typep (cdr (car (cdr data))) 'string))) ; :status value |
||||
(is (equal t (or (string-equal (cdr (car (cdr data))) "off") |
||||
(string-equal (cdr (car (cdr data))) "on")))))) |
||||
|
||||
(fiveam:test gallery-2-status-data |
||||
:description "Validates the (status) data produced by `GALLERY2'." |
||||
;; Data should still take the same shape as others: |
||||
;; ((:ID . 1) (:STATUS . "off") (:TIME . "2021-04-26T20:42:19.400868")) |
||||
(let ((data (ritherdon-rest:parse-request "/status/latest/5"))) |
||||
(is (= 3 (length data))) |
||||
(is (equal t (consp data))) |
||||
(is (equal ':id (first (nth 0 data)))) |
||||
(is (equal ':status (first (nth 1 data)))) |
||||
(is (equal ':time (first (nth 2 data)))) |
||||
(is (equal t (typep (cdr (car data)) 'integer))) ; row :id number |
||||
(is (> (cdr (car data)) 0)) |
||||
(is (equal t (typep (cdr (car (cdr data))) 'string))) ; :status value |
||||
(is (equal t (or (string-equal (cdr (car (cdr data))) "off") |
||||
(string-equal (cdr (car (cdr data))) "on")))))) |
||||
|
||||
(fiveam:test gallery-3-status-data |
||||
:description "Validates the (status) data produced by `GALLERY3'." |
||||
;; This corresponds to 'factory3' -- no light sensor in welding |
||||
;; booth 3 in Ritherdon. Therefore, no data to send. Because of |
||||
;; this, the data returned here should be the 'seed data' unless |
||||
;; I've manually updated it as part of another test. |
||||
;; Data should still take the same shape as others: |
||||
;; ((:ID . 1) (:STATUS . "off") (:TIME . "2021-04-26T20:42:19.400868")). |
||||
(let ((data (ritherdon-rest:parse-request "/status/latest/6"))) |
||||
(is (= 3 (length data))) |
||||
(is (equal t (consp data))) |
||||
(is (equal ':id (first (nth 0 data)))) |
||||
(is (equal ':status (first (nth 1 data)))) |
||||
(is (equal ':time (first (nth 2 data)))) |
||||
(is (equal t (typep (cdr (car data)) 'integer))) ; row :id number |
||||
(is (> (cdr (car data)) 0)) |
||||
(is (equal t (typep (cdr (car (cdr data))) 'string))) ; :status value |
||||
(is (equal t (or (string-equal (cdr (car (cdr data))) "off") |
||||
(string-equal (cdr (car (cdr data))) "on")))))) |
||||
|
||||
|
||||
;;; LIGHT READINGS TESTS |
||||
;;; ==================== |
||||
|
||||
;;; Theses tests check the 'shape' of the light meter readings |
||||
;;; returned by the HTTP (REST) requests. They make sure the data |
||||
;;; matches what's expected. These tests are expecting JSON data along |
||||
;;; the lines of: |
||||
|
||||
;;; ((:ID . 1855109) (:READING . 16) (:TIME . "2021-07-02T13:42:16")) |
||||
|
||||
;;; ID: The row Id. in the database. |
||||
;;; READING: The amount of light recorded. |
||||
;;; TIME: The timestamp when the reading was taken. |
||||
|
||||
;;; Because of how the light meters work, they do not always take |
||||
;;; light meter readings at consistent intervals. But, both devices |
||||
;;; aim to take a reading for as long as they are on. Also, Ritherdon |
||||
;;; has three welding booths and the system has the capacity to record |
||||
;;; all three booths. The Return to Ritherdon project decided on only |
||||
;;; recording two of the booths so 'factory3' should only return it |
||||
;;; seed data -- unless I've manually updated it (for another test |
||||
;;; most likely). |
||||
|
||||
(fiveam:test factory-1-reading-data ; Had to add fiveam here because |
||||
; of naming conflict with ratify |
||||
; package. Comment at top of file |
||||
; explaining further. |
||||
:description "Validates the (light reading) data produced by `FACTORY1'." |
||||
;; ((:ID . 1855109) (:READING . 16) (:TIME . "2021-07-02T13:42:16")) |
||||
(let ((data (ritherdon-rest:parse-request "/readings/latest/1"))) |
||||
(is-true (= 3 (length data))) |
||||
(is-true (consp data)) |
||||
(is (equal ':id (caar data))) |
||||
(is (equal ':reading (caadr data))) |
||||
(is (equal ':time (caaddr data))) |
||||
(is-true (> (cdar data) 0)) ; Db Id numbers start at 0. |
||||
(is-true (typep (cdadr data) 'integer)) ; reading can be <=> 0. |
||||
;; Returns the date-time if data could be parsed. Returns `NIL' if |
||||
;; it could not. Don't need to check if string. If system can't |
||||
;; parse it doesn't matter what form the date-time is in. |
||||
(is-false (equal (ratify:datetime-p (cdaddr data)) nil)))) ; time value. |
||||
|
||||
(fiveam:test factory-2-reading-data |
||||
:description "Validates the (light reading) data produced by `FACTORY2'." |
||||
;; ((:ID . 1855109) (:READING . 16) (:TIME . "2021-07-02T13:42:16")) |
||||
(let ((data (ritherdon-rest:parse-request "/readings/latest/2"))) |
||||
(is-true (= 3 (length data))) |
||||
(is-true (consp data)) |
||||
(is (equal ':id (caar data))) |
||||
(is (equal ':reading (caadr data))) |
||||
(is (equal ':time (caaddr data))) |
||||
(is-true (> (cdar data) 0)) ; Db Id numbers start at 0. |
||||
(is-true (typep (cdadr data) 'integer)) ; reading can be <=> 0. |
||||
;; Returns the date-time if data could be parsed. Returns `NIL' if |
||||
;; it could not. Don't need to check if string. If system can't |
||||
;; parse it doesn't matter what form the date-time is in. |
||||
(is-false (equal (ratify:datetime-p (cdaddr data)) nil)))) ; time value. |
||||
|
||||
(fiveam:test factory-3-reading-data |
||||
:description "Validates the (light reading) data produced by `FACTORY3'." |
||||
;; ((:ID . 1855109) (:READING . 16) (:TIME . "2021-07-02T13:42:16")) |
||||
(let ((data (ritherdon-rest:parse-request "/readings/latest/3"))) |
||||
(is-true (= 3 (length data))) |
||||
(is-true (consp data)) |
||||
(is (equal ':id (caar data))) |
||||
(is (equal ':reading (caadr data))) |
||||
(is (equal ':time (caaddr data))) |
||||
(is-true (> (cdar data) 0)) ; Db Id numbers start at 0. |
||||
(is-true (typep (cdadr data) 'integer)) ; reading can be <=> 0. |
||||
;; This device is not active so the seed data should be |
||||
;; returned. This data should not be produce `NIL' when Ratify |
||||
;; tries to parse it (not a valid timestamp). If the timestamp can |
||||
;; be parsed it most likely means I've updated the latest reading |
||||
;; data (manually most likely) which should result in this failing |
||||
;; because Ratify will return the parsed timestamp and not `NIL'. |
||||
(is (equal (ratify:datetime-p (cdaddr data)) nil)))) ; time value. |
||||
|
||||
(fiveam:test all-device-status-data |
||||
:description "Validates the status data produced when all status |
||||
data for all devices is requested in the same API call." |
||||
(let ((data (ritherdon-rest:parse-request "/status/latest"))) |
||||
(is (= 6 (length data))) |
||||
(is-true (consp data)) |
||||
(is (equal ':|DEVICE 1| (car (first data)))) |
||||
(is (equal ':|DEVICE 2| (car (second data)))) |
||||
(is (equal ':|DEVICE 3| (car (third data)))) |
||||
(is (equal ':|DEVICE 4| (car (fourth data)))) |
||||
(is (equal ':|DEVICE 5| (car (fifth data)))) |
||||
;; if 6 not last a big change has occurred. |
||||
(is (equal ':|DEVICE 6| (caar (last data)))))) |
Reference in new issue