1
0
Fork 0
Browse Source

Squashed commit of the following:

commit d6f93b022d
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.

commit 75d4748150
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.

commit 4d8a1e6f9b
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.

commit c9559f0a67
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.

commit 2ebd6d2a0f
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.

commit 77952ad294
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.

commit d11119bfdc
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.

commit b055b97e2a
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).

commit 8c87cf5b37
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.

commit 09dd5f9b6e
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.

commit e912fdc73d
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.

commit 05b876bea7
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.

commit 8f8cd147ce
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.

commit 943a912d96
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.

commit 26b025361d
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.

commit aa1abec7fe
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.

commit 00ba38f3b0
Author: Craig Oates <craig@craigoates.net>
Date:   Mon Jun 28 23:11:46 2021 +0100

    create project files.
master
Craig Oates 3 years ago
parent
commit
a6c1f15a7b
  1. 1
      .gitignore
  2. 2
      LICENSE
  3. 4
      README.md
  4. 34
      ritherdon-rest.asd
  5. 7
      src/package.lisp
  6. 37
      src/ritherdon-rest.lisp
  7. 255
      tests/main.lisp
  8. 9
      tests/package.lisp

1
.gitignore vendored

@ -17,3 +17,4 @@
*.wx64fsl
*.wx32fsl
*.directory

2
LICENSE

@ -1,4 +1,4 @@
MIT License Copyright (c) <year> <copyright holders>
MIT License Copyright (c) 2021 Craig Oates
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal

4
README.md

@ -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.

34
ritherdon-rest.asd

@ -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)))

7
src/package.lisp

@ -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.

37
src/ritherdon-rest.lisp

@ -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)))

255
tests/main.lisp

@ -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))))))

9
tests/package.lisp

@ -0,0 +1,9 @@
;;;; tests/package.lisp
(defpackage #:ritherdon-rest-tests
(:use #:cl
#:fiveam
#:ratify)
(:export #:run!
#:all-tests
#:test-quasi))