mirror of
https://github.com/Smaug123/WoofWare.Myriad
synced 2025-10-06 12:38:40 +00:00
Add PureGym tests (#13)
This commit is contained in:
@@ -17,6 +17,10 @@
|
||||
<Compile Include="JsonRecord.fs" />
|
||||
<Compile Include="GeneratedJson.fs"> <!--1-->
|
||||
<MyriadFile>JsonRecord.fs</MyriadFile> <!--2-->
|
||||
</Compile>
|
||||
<Compile Include="PureGymDto.fs" />
|
||||
<Compile Include="GeneratedPureGymDto.fs">
|
||||
<MyriadFile>PureGymDto.fs</MyriadFile> <!--2-->
|
||||
</Compile>
|
||||
<None Include="..\runmyriad.sh">
|
||||
<Link>runmyriad.sh</Link>
|
||||
|
350
ConsumePlugin/GeneratedPureGymDto.fs
Normal file
350
ConsumePlugin/GeneratedPureGymDto.fs
Normal file
@@ -0,0 +1,350 @@
|
||||
//------------------------------------------------------------------------------
|
||||
// This code was generated by myriad.
|
||||
// Changes to this file will be lost when the code is regenerated.
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
namespace PureGym
|
||||
|
||||
/// Module containing JSON parsing methods for the GymOpeningHours type
|
||||
[<RequireQualifiedAccess>]
|
||||
[<CompilationRepresentation(CompilationRepresentationFlags.ModuleSuffix)>]
|
||||
module GymOpeningHours =
|
||||
/// Parse from a JSON node.
|
||||
let jsonParse (node : System.Text.Json.Nodes.JsonNode) : GymOpeningHours =
|
||||
let OpeningHours =
|
||||
node.["openingHours"].AsArray ()
|
||||
|> Seq.map (fun elt -> elt.AsValue().GetValue<string> ())
|
||||
|> List.ofSeq
|
||||
|
||||
let IsAlwaysOpen = node.["isAlwaysOpen"].AsValue().GetValue<bool> ()
|
||||
|
||||
{
|
||||
IsAlwaysOpen = IsAlwaysOpen
|
||||
OpeningHours = OpeningHours
|
||||
}
|
||||
namespace PureGym
|
||||
|
||||
/// Module containing JSON parsing methods for the GymAccessOptions type
|
||||
[<RequireQualifiedAccess>]
|
||||
[<CompilationRepresentation(CompilationRepresentationFlags.ModuleSuffix)>]
|
||||
module GymAccessOptions =
|
||||
/// Parse from a JSON node.
|
||||
let jsonParse (node : System.Text.Json.Nodes.JsonNode) : GymAccessOptions =
|
||||
let QrCodeAccess = node.["qrCodeAccess"].AsValue().GetValue<bool> ()
|
||||
let PinAccess = node.["pinAccess"].AsValue().GetValue<bool> ()
|
||||
|
||||
{
|
||||
PinAccess = PinAccess
|
||||
QrCodeAccess = QrCodeAccess
|
||||
}
|
||||
namespace PureGym
|
||||
|
||||
/// Module containing JSON parsing methods for the GymLocation type
|
||||
[<RequireQualifiedAccess>]
|
||||
[<CompilationRepresentation(CompilationRepresentationFlags.ModuleSuffix)>]
|
||||
module GymLocation =
|
||||
/// Parse from a JSON node.
|
||||
let jsonParse (node : System.Text.Json.Nodes.JsonNode) : GymLocation =
|
||||
let Latitude =
|
||||
try
|
||||
node.["latitude"].AsValue().GetValue<float> ()
|
||||
with :? System.InvalidOperationException as exc ->
|
||||
if exc.Message.Contains "cannot be converted to" then
|
||||
if
|
||||
System.Text.Json.Serialization.JsonNumberHandling.AllowReadingFromString = System.Text.Json.Serialization.JsonNumberHandling.AllowReadingFromString
|
||||
then
|
||||
node.["latitude"].AsValue().GetValue<string> () |> System.Double.Parse
|
||||
else
|
||||
reraise ()
|
||||
else
|
||||
reraise ()
|
||||
|
||||
let Longitude =
|
||||
try
|
||||
node.["longitude"].AsValue().GetValue<float> ()
|
||||
with :? System.InvalidOperationException as exc ->
|
||||
if exc.Message.Contains "cannot be converted to" then
|
||||
if
|
||||
System.Text.Json.Serialization.JsonNumberHandling.AllowReadingFromString = System.Text.Json.Serialization.JsonNumberHandling.AllowReadingFromString
|
||||
then
|
||||
node.["longitude"].AsValue().GetValue<string> () |> System.Double.Parse
|
||||
else
|
||||
reraise ()
|
||||
else
|
||||
reraise ()
|
||||
|
||||
{
|
||||
Longitude = Longitude
|
||||
Latitude = Latitude
|
||||
}
|
||||
namespace PureGym
|
||||
|
||||
/// Module containing JSON parsing methods for the GymAddress type
|
||||
[<RequireQualifiedAccess>]
|
||||
[<CompilationRepresentation(CompilationRepresentationFlags.ModuleSuffix)>]
|
||||
module GymAddress =
|
||||
/// Parse from a JSON node.
|
||||
let jsonParse (node : System.Text.Json.Nodes.JsonNode) : GymAddress =
|
||||
let Postcode = node.["postcode"].AsValue().GetValue<string> ()
|
||||
|
||||
let County =
|
||||
match node.["county"] with
|
||||
| null -> None
|
||||
| v -> v.AsValue().GetValue<string> () |> Some
|
||||
|
||||
let Town = node.["town"].AsValue().GetValue<string> ()
|
||||
|
||||
let AddressLine3 =
|
||||
match node.["addressLine3"] with
|
||||
| null -> None
|
||||
| v -> v.AsValue().GetValue<string> () |> Some
|
||||
|
||||
let AddressLine2 =
|
||||
match node.["addressLine2"] with
|
||||
| null -> None
|
||||
| v -> v.AsValue().GetValue<string> () |> Some
|
||||
|
||||
let AddressLine1 = node.["addressLine1"].AsValue().GetValue<string> ()
|
||||
|
||||
{
|
||||
AddressLine1 = AddressLine1
|
||||
AddressLine2 = AddressLine2
|
||||
AddressLine3 = AddressLine3
|
||||
Town = Town
|
||||
County = County
|
||||
Postcode = Postcode
|
||||
}
|
||||
namespace PureGym
|
||||
|
||||
/// Module containing JSON parsing methods for the Gym type
|
||||
[<RequireQualifiedAccess>]
|
||||
[<CompilationRepresentation(CompilationRepresentationFlags.ModuleSuffix)>]
|
||||
module Gym =
|
||||
/// Parse from a JSON node.
|
||||
let jsonParse (node : System.Text.Json.Nodes.JsonNode) : Gym =
|
||||
let ReopenDate = node.["reopenDate"].AsValue().GetValue<string> ()
|
||||
let TimeZone = node.["timeZone"].AsValue().GetValue<string> ()
|
||||
let Location = GymLocation.jsonParse node.["location"]
|
||||
let AccessOptions = GymAccessOptions.jsonParse node.["accessOptions"]
|
||||
let GymOpeningHours = GymOpeningHours.jsonParse node.["gymOpeningHours"]
|
||||
let EmailAddress = node.["emailAddress"].AsValue().GetValue<string> ()
|
||||
let PhoneNumber = node.["phoneNumber"].AsValue().GetValue<string> ()
|
||||
let Address = GymAddress.jsonParse node.["address"]
|
||||
let Status = node.["status"].AsValue().GetValue<int> ()
|
||||
let Id = node.["id"].AsValue().GetValue<int> ()
|
||||
let Name = node.["name"].AsValue().GetValue<string> ()
|
||||
|
||||
{
|
||||
Name = Name
|
||||
Id = Id
|
||||
Status = Status
|
||||
Address = Address
|
||||
PhoneNumber = PhoneNumber
|
||||
EmailAddress = EmailAddress
|
||||
GymOpeningHours = GymOpeningHours
|
||||
AccessOptions = AccessOptions
|
||||
Location = Location
|
||||
TimeZone = TimeZone
|
||||
ReopenDate = ReopenDate
|
||||
}
|
||||
namespace PureGym
|
||||
|
||||
/// Module containing JSON parsing methods for the Member type
|
||||
[<RequireQualifiedAccess>]
|
||||
[<CompilationRepresentation(CompilationRepresentationFlags.ModuleSuffix)>]
|
||||
module Member =
|
||||
/// Parse from a JSON node.
|
||||
let jsonParse (node : System.Text.Json.Nodes.JsonNode) : Member =
|
||||
let MemberStatus = node.["memberStatus"].AsValue().GetValue<int> ()
|
||||
let SuspendedReason = node.["suspendedReason"].AsValue().GetValue<int> ()
|
||||
let MembershipLevel = node.["membershipLevel"].AsValue().GetValue<int> ()
|
||||
let MembershipName = node.["membershipName"].AsValue().GetValue<string> ()
|
||||
let Postcode = node.["postCode"].AsValue().GetValue<string> ()
|
||||
let MobileNumber = node.["mobileNumber"].AsValue().GetValue<string> ()
|
||||
|
||||
let DateOfBirth =
|
||||
node.["dateofBirth"].AsValue().GetValue<string> () |> System.DateOnly.Parse
|
||||
|
||||
let GymAccessPin = node.["gymAccessPin"].AsValue().GetValue<string> ()
|
||||
let EmailAddress = node.["emailAddress"].AsValue().GetValue<string> ()
|
||||
let HomeGymName = node.["homeGymName"].AsValue().GetValue<string> ()
|
||||
let HomeGymId = node.["homeGymId"].AsValue().GetValue<int> ()
|
||||
let LastName = node.["lastName"].AsValue().GetValue<string> ()
|
||||
let FirstName = node.["firstName"].AsValue().GetValue<string> ()
|
||||
let CompoundMemberId = node.["compoundMemberId"].AsValue().GetValue<string> ()
|
||||
let Id = node.["id"].AsValue().GetValue<int> ()
|
||||
|
||||
{
|
||||
Id = Id
|
||||
CompoundMemberId = CompoundMemberId
|
||||
FirstName = FirstName
|
||||
LastName = LastName
|
||||
HomeGymId = HomeGymId
|
||||
HomeGymName = HomeGymName
|
||||
EmailAddress = EmailAddress
|
||||
GymAccessPin = GymAccessPin
|
||||
DateOfBirth = DateOfBirth
|
||||
MobileNumber = MobileNumber
|
||||
Postcode = Postcode
|
||||
MembershipName = MembershipName
|
||||
MembershipLevel = MembershipLevel
|
||||
SuspendedReason = SuspendedReason
|
||||
MemberStatus = MemberStatus
|
||||
}
|
||||
namespace PureGym
|
||||
|
||||
/// Module containing JSON parsing methods for the GymAttendance type
|
||||
[<RequireQualifiedAccess>]
|
||||
[<CompilationRepresentation(CompilationRepresentationFlags.ModuleSuffix)>]
|
||||
module GymAttendance =
|
||||
/// Parse from a JSON node.
|
||||
let jsonParse (node : System.Text.Json.Nodes.JsonNode) : GymAttendance =
|
||||
let MaximumCapacity = node.["maximumCapacity"].AsValue().GetValue<int> ()
|
||||
|
||||
let LastRefreshedPeopleInClasses =
|
||||
node.["lastRefreshedPeopleInClasses"].AsValue().GetValue<string> ()
|
||||
|> System.DateTime.Parse
|
||||
|
||||
let LastRefreshed =
|
||||
node.["lastRefreshed"].AsValue().GetValue<string> () |> System.DateTime.Parse
|
||||
|
||||
let AttendanceTime =
|
||||
node.["attendanceTime"].AsValue().GetValue<string> () |> System.DateTime.Parse
|
||||
|
||||
let IsApproximate = node.["isApproximate"].AsValue().GetValue<bool> ()
|
||||
|
||||
let TotalPeopleSuffix =
|
||||
match node.["totalPeopleSuffix"] with
|
||||
| null -> None
|
||||
| v -> v.AsValue().GetValue<string> () |> Some
|
||||
|
||||
let TotalPeopleInClasses = node.["totalPeopleInClasses"].AsValue().GetValue<int> ()
|
||||
let TotalPeopleInGym = node.["totalPeopleInGym"].AsValue().GetValue<int> ()
|
||||
let Description = node.["description"].AsValue().GetValue<string> ()
|
||||
|
||||
{
|
||||
Description = Description
|
||||
TotalPeopleInGym = TotalPeopleInGym
|
||||
TotalPeopleInClasses = TotalPeopleInClasses
|
||||
TotalPeopleSuffix = TotalPeopleSuffix
|
||||
IsApproximate = IsApproximate
|
||||
AttendanceTime = AttendanceTime
|
||||
LastRefreshed = LastRefreshed
|
||||
LastRefreshedPeopleInClasses = LastRefreshedPeopleInClasses
|
||||
MaximumCapacity = MaximumCapacity
|
||||
}
|
||||
namespace PureGym
|
||||
|
||||
/// Module containing JSON parsing methods for the MemberActivityDto type
|
||||
[<RequireQualifiedAccess>]
|
||||
[<CompilationRepresentation(CompilationRepresentationFlags.ModuleSuffix)>]
|
||||
module MemberActivityDto =
|
||||
/// Parse from a JSON node.
|
||||
let jsonParse (node : System.Text.Json.Nodes.JsonNode) : MemberActivityDto =
|
||||
let LastRefreshed =
|
||||
node.["lastRefreshed"].AsValue().GetValue<string> () |> System.DateTime.Parse
|
||||
|
||||
let IsEstimated = node.["isEstimated"].AsValue().GetValue<bool> ()
|
||||
let TotalClasses = node.["totalClasses"].AsValue().GetValue<int> ()
|
||||
let TotalVisits = node.["totalVisits"].AsValue().GetValue<int> ()
|
||||
let AverageDuration = node.["averageDuration"].AsValue().GetValue<int> ()
|
||||
let TotalDuration = node.["totalDuration"].AsValue().GetValue<int> ()
|
||||
|
||||
{
|
||||
TotalDuration = TotalDuration
|
||||
AverageDuration = AverageDuration
|
||||
TotalVisits = TotalVisits
|
||||
TotalClasses = TotalClasses
|
||||
IsEstimated = IsEstimated
|
||||
LastRefreshed = LastRefreshed
|
||||
}
|
||||
namespace PureGym
|
||||
|
||||
/// Module containing JSON parsing methods for the SessionsAggregate type
|
||||
[<RequireQualifiedAccess>]
|
||||
[<CompilationRepresentation(CompilationRepresentationFlags.ModuleSuffix)>]
|
||||
module SessionsAggregate =
|
||||
/// Parse from a JSON node.
|
||||
let jsonParse (node : System.Text.Json.Nodes.JsonNode) : SessionsAggregate =
|
||||
let Duration = node.["duration"].AsValue().GetValue<int> ()
|
||||
let Visits = node.["visits"].AsValue().GetValue<int> ()
|
||||
let Activities = node.["activities"].AsValue().GetValue<int> ()
|
||||
|
||||
{
|
||||
Activities = Activities
|
||||
Visits = Visits
|
||||
Duration = Duration
|
||||
}
|
||||
namespace PureGym
|
||||
|
||||
/// Module containing JSON parsing methods for the VisitGym type
|
||||
[<RequireQualifiedAccess>]
|
||||
[<CompilationRepresentation(CompilationRepresentationFlags.ModuleSuffix)>]
|
||||
module VisitGym =
|
||||
/// Parse from a JSON node.
|
||||
let jsonParse (node : System.Text.Json.Nodes.JsonNode) : VisitGym =
|
||||
let Status = node.["status"].AsValue().GetValue<string> ()
|
||||
let Name = node.["name"].AsValue().GetValue<string> ()
|
||||
let Id = node.["id"].AsValue().GetValue<int> ()
|
||||
|
||||
{
|
||||
Id = Id
|
||||
Name = Name
|
||||
Status = Status
|
||||
}
|
||||
namespace PureGym
|
||||
|
||||
/// Module containing JSON parsing methods for the Visit type
|
||||
[<RequireQualifiedAccess>]
|
||||
[<CompilationRepresentation(CompilationRepresentationFlags.ModuleSuffix)>]
|
||||
module Visit =
|
||||
/// Parse from a JSON node.
|
||||
let jsonParse (node : System.Text.Json.Nodes.JsonNode) : Visit =
|
||||
let Gym = VisitGym.jsonParse node.["gym"]
|
||||
let Duration = node.["duration"].AsValue().GetValue<int> ()
|
||||
|
||||
let StartTime =
|
||||
node.["startTime"].AsValue().GetValue<string> () |> System.DateTime.Parse
|
||||
|
||||
let IsDurationEstimated = node.["isDurationEstimated"].AsValue().GetValue<bool> ()
|
||||
|
||||
{
|
||||
IsDurationEstimated = IsDurationEstimated
|
||||
StartTime = StartTime
|
||||
Duration = Duration
|
||||
Gym = Gym
|
||||
}
|
||||
namespace PureGym
|
||||
|
||||
/// Module containing JSON parsing methods for the SessionsSummary type
|
||||
[<RequireQualifiedAccess>]
|
||||
[<CompilationRepresentation(CompilationRepresentationFlags.ModuleSuffix)>]
|
||||
module SessionsSummary =
|
||||
/// Parse from a JSON node.
|
||||
let jsonParse (node : System.Text.Json.Nodes.JsonNode) : SessionsSummary =
|
||||
let ThisWeek = SessionsAggregate.jsonParse node.["thisWeek"]
|
||||
let Total = SessionsAggregate.jsonParse node.["total"]
|
||||
|
||||
{
|
||||
Total = Total
|
||||
ThisWeek = ThisWeek
|
||||
}
|
||||
namespace PureGym
|
||||
|
||||
/// Module containing JSON parsing methods for the Sessions type
|
||||
[<RequireQualifiedAccess>]
|
||||
[<CompilationRepresentation(CompilationRepresentationFlags.ModuleSuffix)>]
|
||||
module Sessions =
|
||||
/// Parse from a JSON node.
|
||||
let jsonParse (node : System.Text.Json.Nodes.JsonNode) : Sessions =
|
||||
let Visits =
|
||||
node.["visits"].AsArray ()
|
||||
|> Seq.map (fun elt -> Visit.jsonParse elt)
|
||||
|> List.ofSeq
|
||||
|
||||
let Summary = SessionsSummary.jsonParse node.["summary"]
|
||||
|
||||
{
|
||||
Summary = Summary
|
||||
Visits = Visits
|
||||
}
|
317
ConsumePlugin/PureGymDto.fs
Normal file
317
ConsumePlugin/PureGymDto.fs
Normal file
@@ -0,0 +1,317 @@
|
||||
// Copied from https://gitea.patrickstevens.co.uk/patrick/puregym-unofficial-dotnet/src/commit/2741c5e36cf0bdb203b12b78a8062e25af9d89c7/PureGym/Api.fs
|
||||
|
||||
namespace PureGym
|
||||
|
||||
open System
|
||||
open System.Text.Json.Serialization
|
||||
|
||||
/// Describes the opening hours of a given gym.
|
||||
[<MyriadPlugin.JsonParse>]
|
||||
type GymOpeningHours =
|
||||
{
|
||||
/// If this is true, there should be no OpeningHours (but nothing enforces that).
|
||||
IsAlwaysOpen : bool
|
||||
/// This is a pretty unstructured list, which is in general not really parseable: it's human-readable only.
|
||||
OpeningHours : string list
|
||||
}
|
||||
|
||||
/// Human-readable representation
|
||||
override this.ToString () =
|
||||
if this.IsAlwaysOpen then
|
||||
"always open"
|
||||
else
|
||||
this.OpeningHours |> String.concat ", "
|
||||
|
||||
/// How a human can authenticate with a gym when they physically try to enter it
|
||||
[<MyriadPlugin.JsonParse>]
|
||||
type GymAccessOptions =
|
||||
{
|
||||
/// This gym has PIN entry pads
|
||||
PinAccess : bool
|
||||
/// This gym has a QR code scanner. QR codes can be generated with the PureGym app.
|
||||
QrCodeAccess : bool
|
||||
}
|
||||
|
||||
/// Where a gym is on the Earth
|
||||
[<MyriadPlugin.JsonParse>]
|
||||
type GymLocation =
|
||||
{
|
||||
/// Measured in degrees
|
||||
[<JsonNumberHandling(JsonNumberHandling.AllowReadingFromString)>]
|
||||
Longitude : float
|
||||
/// Measured in degrees
|
||||
[<JsonNumberHandling(JsonNumberHandling.AllowReadingFromString)>]
|
||||
Latitude : float
|
||||
}
|
||||
|
||||
/// The postal address of a gym
|
||||
[<MyriadPlugin.JsonParse>]
|
||||
type GymAddress =
|
||||
{
|
||||
/// E.g. "Canterbury Court"
|
||||
[<JsonRequired>]
|
||||
AddressLine1 : string
|
||||
/// E.g. "Units 4, 4A, 5 And 5A"
|
||||
AddressLine2 : string option
|
||||
/// E.g. "Kennington Park"
|
||||
AddressLine3 : string option
|
||||
/// E.g. "LONDON"
|
||||
[<JsonRequired>]
|
||||
Town : string
|
||||
County : string option
|
||||
/// E.g. "SW9 6DE"
|
||||
[<JsonRequired>]
|
||||
Postcode : string
|
||||
}
|
||||
|
||||
/// Human-readable statement of the address
|
||||
override this.ToString () =
|
||||
[
|
||||
yield Some this.AddressLine1
|
||||
yield this.AddressLine2
|
||||
yield this.AddressLine3
|
||||
match this.County with
|
||||
| None -> yield Some $"%s{this.Town} %s{this.Postcode}"
|
||||
| Some county ->
|
||||
yield Some this.Town
|
||||
yield Some $"%s{county} %s{this.Postcode}"
|
||||
]
|
||||
|> Seq.choose id
|
||||
|> String.concat "\n"
|
||||
|
||||
/// Metadata about a physical gym
|
||||
[<MyriadPlugin.JsonParse>]
|
||||
type Gym =
|
||||
{
|
||||
// The following fields are returned but are always null
|
||||
// ReasonsToJoin : string
|
||||
// VirtualTourUrl : Uri
|
||||
// PersonalTrainersUrl : Uri
|
||||
// WebViewUrl : Uri
|
||||
// FloorPlanUrl : Uri
|
||||
// StaffMembers : string
|
||||
|
||||
/// The name of this gym, e.g. "London Oval"
|
||||
[<JsonRequired>]
|
||||
Name : string
|
||||
/// This gym's ID in the PureGym system, e.g. 19
|
||||
[<JsonRequired>]
|
||||
Id : int
|
||||
/// I don't know what this status is. Please tell me if you know!
|
||||
[<JsonRequired>]
|
||||
Status : int
|
||||
/// Postal address of this gym
|
||||
[<JsonRequired>]
|
||||
Address : GymAddress
|
||||
/// Phone number of this gym, e.g. "+44 1234 567890"
|
||||
[<JsonRequired>]
|
||||
PhoneNumber : string
|
||||
/// Contact email address for this gym's staff
|
||||
[<JsonRequired>]
|
||||
EmailAddress : string
|
||||
/// When this gym is open
|
||||
[<JsonRequired>]
|
||||
GymOpeningHours : GymOpeningHours
|
||||
/// How a human can physically authenticate when they physically enter this gym
|
||||
[<JsonRequired>]
|
||||
AccessOptions : GymAccessOptions
|
||||
/// Where this gym is physically located
|
||||
[<JsonRequired>]
|
||||
Location : GymLocation
|
||||
/// The IANA time zone this gym observes, e.g. "Europe/London"
|
||||
[<JsonRequired>]
|
||||
TimeZone : string
|
||||
/// This is a date-time in the format yyyy-MM-ddTHH:mm:ss+01 Europe/London
|
||||
ReopenDate : string
|
||||
}
|
||||
|
||||
/// Human-readable representation of the most important information about this gym
|
||||
override this.ToString () =
|
||||
$"""%s{this.Name} (%i{this.Id})
|
||||
{this.Address}
|
||||
%s{this.EmailAddress} %s{this.PhoneNumber}
|
||||
Opening hours: %s{string<GymOpeningHours> this.GymOpeningHours}
|
||||
%s{string<GymAccessOptions> this.AccessOptions}
|
||||
"""
|
||||
|
||||
/// A human member of PureGym
|
||||
[<MyriadPlugin.JsonParse>]
|
||||
type Member =
|
||||
{
|
||||
/// This member's ID. This is a fairly large number.
|
||||
Id : int
|
||||
/// No idea what this is - please tell me if you know!
|
||||
CompoundMemberId : string
|
||||
/// First name, e.g. "Patrick"
|
||||
FirstName : string
|
||||
/// Last name, e.g. "Stevens"
|
||||
LastName : string
|
||||
/// ID of the gym designated as this user's home gym. This is also the "Id" field of the appropriate Gym object.
|
||||
HomeGymId : int
|
||||
/// The name of the gym designated as this user's home gym. This is also the "Name" field of the appropriate
|
||||
/// Gym object.
|
||||
HomeGymName : string
|
||||
/// This user's email address
|
||||
EmailAddress : string
|
||||
/// This user's gym access pin, probably 8 digits
|
||||
GymAccessPin : string
|
||||
/// This user's recorded date of birth
|
||||
[<JsonPropertyName "dateofBirth">]
|
||||
DateOfBirth : DateOnly
|
||||
/// This user's phone number, human-readable
|
||||
MobileNumber : string
|
||||
/// This user's registered home postcode
|
||||
[<JsonPropertyName "postCode">]
|
||||
Postcode : string
|
||||
/// E.g. "Corporate"
|
||||
MembershipName : string
|
||||
MembershipLevel : int
|
||||
SuspendedReason : int
|
||||
MemberStatus : int
|
||||
}
|
||||
|
||||
/// Statistics for how many people are currently at a gym
|
||||
[<MyriadPlugin.JsonParse>]
|
||||
type GymAttendance =
|
||||
{
|
||||
/// This appears always to be just equal to TotalPeopleInGym, but a string.
|
||||
[<JsonRequired>]
|
||||
Description : string
|
||||
/// How many people are in the gym as of this statistics snapshot
|
||||
[<JsonRequired>]
|
||||
TotalPeopleInGym : int
|
||||
/// How many people are in classes at the gym as of this statistics snapshot
|
||||
[<JsonRequired>]
|
||||
TotalPeopleInClasses : int
|
||||
/// E.g. " or fewer"
|
||||
TotalPeopleSuffix : string option
|
||||
[<JsonRequired>]
|
||||
IsApproximate : bool
|
||||
/// When the query was received (I think)
|
||||
AttendanceTime : DateTime
|
||||
/// When the "total people in gym" snapshot was taken that is reported here
|
||||
LastRefreshed : DateTime
|
||||
/// When the "number of people in classes" snapshot was taken that is reported here
|
||||
LastRefreshedPeopleInClasses : DateTime
|
||||
/// Maximum capacity of the gym, or 0 if no listed capacity
|
||||
MaximumCapacity : int
|
||||
}
|
||||
|
||||
/// The visit statistics for a particular human to a particular gym.
|
||||
/// The semantics of this class are basically unknown.
|
||||
type MemberActivityThisMonth =
|
||||
{
|
||||
/// How many minutes, including classes, have been logged so far this month
|
||||
TotalDurationMinutes : int
|
||||
/// How long, in minutes, each visit has been on average this month
|
||||
AverageDurationMinutes : int
|
||||
/// How many visits have been made this month, excluding classes
|
||||
TotalVisits : int
|
||||
/// How many classes have been attended this month
|
||||
TotalClasses : int
|
||||
/// Whether this block of statistics is estimated rather than exact
|
||||
IsEstimated : bool
|
||||
/// When this data was constructed
|
||||
LastRefreshed : DateTime
|
||||
}
|
||||
|
||||
/// Don't use this type. It's public because System.Text.Json can't do private types.
|
||||
[<MyriadPlugin.JsonParse>]
|
||||
type MemberActivityDto =
|
||||
{
|
||||
[<JsonRequired>]
|
||||
TotalDuration : int
|
||||
[<JsonRequired>]
|
||||
AverageDuration : int
|
||||
[<JsonRequired>]
|
||||
TotalVisits : int
|
||||
[<JsonRequired>]
|
||||
TotalClasses : int
|
||||
[<JsonRequired>]
|
||||
IsEstimated : bool
|
||||
[<JsonRequired>]
|
||||
LastRefreshed : DateTime
|
||||
}
|
||||
|
||||
member this.ToMemberActivity () =
|
||||
{
|
||||
TotalDurationMinutes = this.TotalDuration
|
||||
AverageDurationMinutes = this.AverageDuration
|
||||
TotalVisits = this.TotalVisits
|
||||
TotalClasses = this.TotalClasses
|
||||
IsEstimated = this.IsEstimated
|
||||
LastRefreshed = this.LastRefreshed
|
||||
}
|
||||
|
||||
[<MyriadPlugin.JsonParse>]
|
||||
type SessionsAggregate =
|
||||
{
|
||||
/// Number of gym "activities" within some query-defined time period; presumably this is like classes?
|
||||
/// It's always 0 for me.
|
||||
Activities : int
|
||||
/// Number of visits to the gym within some query-defined time period.
|
||||
Visits : int
|
||||
/// In minutes: total time spent in gym during the query-defined time period.
|
||||
Duration : int
|
||||
}
|
||||
|
||||
/// The DTO for gym info returned from the Sessions endpoint.
|
||||
[<MyriadPlugin.JsonParse>]
|
||||
type VisitGym =
|
||||
{
|
||||
// Omitting Location, GymAccess, ContactInfo, TimeZone because these were all null for me
|
||||
/// The PureGym ID of this gym, e.g. 19
|
||||
Id : int
|
||||
/// E.g. "London Oval", the canonical name of this gym
|
||||
Name : string
|
||||
/// For some reason this always seems to be "Blocked"
|
||||
Status : string
|
||||
}
|
||||
|
||||
/// Summary of a single visit to a gym.
|
||||
[<MyriadPlugin.JsonParse>]
|
||||
type Visit =
|
||||
{
|
||||
// Omitted Name because it always was null for me
|
||||
/// Whether the Duration field is estimated.
|
||||
IsDurationEstimated : bool
|
||||
/// When the visit began.
|
||||
StartTime : DateTime
|
||||
/// In minutes.
|
||||
Duration : int
|
||||
/// Which gym was visited
|
||||
Gym : VisitGym
|
||||
}
|
||||
|
||||
/// Human-readable non-round-trip representation.
|
||||
override this.ToString () =
|
||||
let startTime = this.StartTime.ToString "yyyy-MM-dd HH:mm"
|
||||
$"%s{this.Gym.Name}: %s{startTime} (%i{this.Duration} minutes)"
|
||||
|
||||
/// Aggregate statistics for gym visits across a time period.
|
||||
[<MyriadPlugin.JsonParse>]
|
||||
type SessionsSummary =
|
||||
{
|
||||
/// Aggregate stats for gym visits within the query-dependent time period.
|
||||
Total : SessionsAggregate
|
||||
/// Aggregate stats for gym visits "this week", whatever that means to PureGym.
|
||||
ThisWeek : SessionsAggregate
|
||||
}
|
||||
|
||||
/// Human-readable non-round-trip representation.
|
||||
override this.ToString () =
|
||||
$"%i{this.Total.Visits} visits, totalling %i{this.Total.Duration} minutes"
|
||||
|
||||
[<MyriadPlugin.JsonParse>]
|
||||
type Sessions =
|
||||
{
|
||||
Summary : SessionsSummary
|
||||
Visits : Visit list
|
||||
}
|
||||
|
||||
/// Human-readable non-round-trip representation.
|
||||
override this.ToString () =
|
||||
let summary = string<SessionsSummary> this.Summary
|
||||
let visits = this.Visits |> Seq.map string<Visit> |> String.concat "\n"
|
||||
|
||||
$"%s{summary}\n%s{visits}"
|
@@ -8,6 +8,7 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Compile Include="TestPureGymJson.fs" />
|
||||
<Compile Include="TestSurface.fs" />
|
||||
<Compile Include="TestRemoveOptions.fs" />
|
||||
<Compile Include="TestJsonParse.fs" />
|
||||
|
312
MyriadPlugin.Test/TestPureGymJson.fs
Normal file
312
MyriadPlugin.Test/TestPureGymJson.fs
Normal file
@@ -0,0 +1,312 @@
|
||||
namespace PureGym.Test
|
||||
|
||||
open System
|
||||
open System.Text.Json.Nodes
|
||||
open NUnit.Framework
|
||||
open FsUnitTyped
|
||||
open PureGym
|
||||
|
||||
[<TestFixture>]
|
||||
module TestPureGymJson =
|
||||
|
||||
let gymOpeningHoursCases =
|
||||
[
|
||||
"""{"openingHours": [], "isAlwaysOpen": false}""",
|
||||
{
|
||||
GymOpeningHours.OpeningHours = []
|
||||
IsAlwaysOpen = false
|
||||
}
|
||||
"""{"openingHours": ["something"], "isAlwaysOpen": false}""",
|
||||
{
|
||||
GymOpeningHours.OpeningHours = [ "something" ]
|
||||
IsAlwaysOpen = false
|
||||
}
|
||||
]
|
||||
|> List.map TestCaseData
|
||||
|
||||
[<TestCaseSource(nameof (gymOpeningHoursCases))>]
|
||||
let ``GymOpeningHours JSON parse`` (json : string, expected : GymOpeningHours) =
|
||||
JsonNode.Parse json |> GymOpeningHours.jsonParse |> shouldEqual expected
|
||||
|
||||
let gymAccessOptionsCases =
|
||||
List.allPairs [ true ; false ] [ true ; false ]
|
||||
|> List.map (fun (a, b) ->
|
||||
let s = sprintf """{"pinAccess": %b, "qrCodeAccess": %b}""" a b
|
||||
|
||||
s,
|
||||
{
|
||||
GymAccessOptions.PinAccess = a
|
||||
QrCodeAccess = b
|
||||
}
|
||||
)
|
||||
|> List.map TestCaseData
|
||||
|
||||
[<TestCaseSource(nameof (gymAccessOptionsCases))>]
|
||||
let ``GymAccessOptions JSON parse`` (json : string, expected : GymAccessOptions) =
|
||||
JsonNode.Parse json |> GymAccessOptions.jsonParse |> shouldEqual expected
|
||||
|
||||
let gymLocationCases =
|
||||
[
|
||||
"""{"latitude": 1.0, "longitude": 3.0}""",
|
||||
{
|
||||
GymLocation.Latitude = 1.0
|
||||
Longitude = 3.0
|
||||
}
|
||||
]
|
||||
|> List.map TestCaseData
|
||||
|
||||
[<TestCaseSource(nameof (gymLocationCases))>]
|
||||
let ``GymLocation JSON parse`` (json : string, expected : GymLocation) =
|
||||
JsonNode.Parse json |> GymLocation.jsonParse |> shouldEqual expected
|
||||
|
||||
let gymAddressCases =
|
||||
[
|
||||
"""{"addressLine1": "", "postCode": "hi", "town": ""}""",
|
||||
{
|
||||
GymAddress.AddressLine1 = ""
|
||||
AddressLine2 = None
|
||||
AddressLine3 = None
|
||||
County = None
|
||||
Postcode = "hi"
|
||||
Town = ""
|
||||
}
|
||||
"""{"addressLine1": "", "addressLine2": null, "postCode": "hi", "town": ""}""",
|
||||
{
|
||||
GymAddress.AddressLine1 = ""
|
||||
AddressLine2 = None
|
||||
AddressLine3 = None
|
||||
County = None
|
||||
Postcode = "hi"
|
||||
Town = ""
|
||||
}
|
||||
]
|
||||
|> List.map TestCaseData
|
||||
|
||||
[<TestCaseSource(nameof (gymAddressCases))>]
|
||||
let ``GymAddress JSON parse`` (json : string, expected : GymAddress) =
|
||||
JsonNode.Parse (json, Nullable (JsonNodeOptions (PropertyNameCaseInsensitive = true)))
|
||||
|> GymAddress.jsonParse
|
||||
|> shouldEqual expected
|
||||
|
||||
let gymCases =
|
||||
let ovalJson =
|
||||
"""{"name":"London Oval","id":19,"status":2,"address":{"addressLine1":"Canterbury Court","addressLine2":"Units 4, 4A, 5 And 5A","addressLine3":"Kennington Park","town":"LONDON","county":null,"postcode":"SW9 6DE"},"phoneNumber":"+44 3444770005","emailAddress":"info.londonoval@puregym.com","staffMembers":null,"gymOpeningHours":{"isAlwaysOpen":true,"openingHours":[]},"reasonsToJoin":null,"accessOptions":{"pinAccess":true,"qrCodeAccess":true},"virtualTourUrl":null,"personalTrainersUrl":null,"webViewUrl":null,"floorPlanUrl":null,"location":{"longitude":"-0.110252","latitude":"51.480401"},"timeZone":"Europe/London","reopenDate":"2021-04-12T00:00:00+01 Europe/London"}"""
|
||||
|
||||
let oval =
|
||||
{
|
||||
Gym.Name = "London Oval"
|
||||
Id = 19
|
||||
Status = 2
|
||||
Address =
|
||||
{
|
||||
AddressLine1 = "Canterbury Court"
|
||||
AddressLine2 = Some "Units 4, 4A, 5 And 5A"
|
||||
AddressLine3 = Some "Kennington Park"
|
||||
Town = "LONDON"
|
||||
County = None
|
||||
Postcode = "SW9 6DE"
|
||||
}
|
||||
PhoneNumber = "+44 3444770005"
|
||||
EmailAddress = "info.londonoval@puregym.com"
|
||||
GymOpeningHours =
|
||||
{
|
||||
IsAlwaysOpen = true
|
||||
OpeningHours = []
|
||||
}
|
||||
AccessOptions =
|
||||
{
|
||||
PinAccess = true
|
||||
QrCodeAccess = true
|
||||
}
|
||||
Location =
|
||||
{
|
||||
Longitude = -0.110252
|
||||
Latitude = 51.480401
|
||||
}
|
||||
TimeZone = "Europe/London"
|
||||
ReopenDate = "2021-04-12T00:00:00+01 Europe/London"
|
||||
}
|
||||
|
||||
[ ovalJson, oval ] |> List.map TestCaseData
|
||||
|
||||
[<TestCaseSource(nameof (gymCases))>]
|
||||
let ``Gym JSON parse`` (json : string, expected : Gym) =
|
||||
JsonNode.Parse json |> Gym.jsonParse |> shouldEqual expected
|
||||
|
||||
let memberCases =
|
||||
let me =
|
||||
{
|
||||
Id = 1234567
|
||||
CompoundMemberId = "12A123456"
|
||||
FirstName = "Patrick"
|
||||
LastName = "Stevens"
|
||||
HomeGymId = 19
|
||||
HomeGymName = "London Oval"
|
||||
EmailAddress = "someone@somewhere"
|
||||
GymAccessPin = "00000000"
|
||||
DateOfBirth = DateOnly (1994, 01, 02)
|
||||
MobileNumber = "+44 1234567"
|
||||
Postcode = "W1A 1AA"
|
||||
MembershipName = "Corporate"
|
||||
MembershipLevel = 12
|
||||
SuspendedReason = 0
|
||||
MemberStatus = 2
|
||||
}
|
||||
|
||||
let meJson =
|
||||
"""{
|
||||
"id": 1234567,
|
||||
"compoundMemberId": "12A123456",
|
||||
"firstName": "Patrick",
|
||||
"lastName": "Stevens",
|
||||
"homeGymId": 19,
|
||||
"homeGymName": "London Oval",
|
||||
"emailAddress": "someone@somewhere",
|
||||
"gymAccessPin": "00000000",
|
||||
"dateofBirth": "1994-01-02",
|
||||
"mobileNumber": "+44 1234567",
|
||||
"postCode": "W1A 1AA",
|
||||
"membershipName": "Corporate",
|
||||
"membershipLevel": 12,
|
||||
"suspendedReason": 0,
|
||||
"memberStatus": 2
|
||||
}"""
|
||||
|
||||
[ meJson, me ] |> List.map TestCaseData
|
||||
|
||||
[<TestCaseSource(nameof memberCases)>]
|
||||
let ``Member JSON parse`` (json : string, expected : Member) =
|
||||
json |> JsonNode.Parse |> Member.jsonParse |> shouldEqual expected
|
||||
|
||||
let gymAttendanceCases =
|
||||
let json =
|
||||
"""{
|
||||
"description": "65",
|
||||
"totalPeopleInGym": 65,
|
||||
"totalPeopleInClasses": 2,
|
||||
"totalPeopleSuffix": null,
|
||||
"isApproximate": false,
|
||||
"attendanceTime": "2023-12-27T18:54:09.5101697",
|
||||
"lastRefreshed": "2023-12-27T18:54:09.5101697Z",
|
||||
"lastRefreshedPeopleInClasses": "2023-12-27T18:50:26.0782286Z",
|
||||
"maximumCapacity": 0
|
||||
}"""
|
||||
|
||||
let expected =
|
||||
{
|
||||
Description = "65"
|
||||
TotalPeopleInGym = 65
|
||||
TotalPeopleInClasses = 2
|
||||
TotalPeopleSuffix = None
|
||||
IsApproximate = false
|
||||
AttendanceTime =
|
||||
DateTime (2023, 12, 27, 18, 54, 09, 510, 169, DateTimeKind.Utc)
|
||||
+ TimeSpan.FromTicks 7L
|
||||
LastRefreshed =
|
||||
DateTime (2023, 12, 27, 18, 54, 09, 510, 169, DateTimeKind.Utc)
|
||||
+ TimeSpan.FromTicks 7L
|
||||
LastRefreshedPeopleInClasses =
|
||||
DateTime (2023, 12, 27, 18, 50, 26, 078, 228, DateTimeKind.Utc)
|
||||
+ TimeSpan.FromTicks 6L
|
||||
MaximumCapacity = 0
|
||||
}
|
||||
|
||||
[ json, expected ] |> List.map TestCaseData
|
||||
|
||||
[<TestCaseSource(nameof gymAttendanceCases)>]
|
||||
let ``GymAttendance JSON parse`` (json : string, expected : GymAttendance) =
|
||||
json |> JsonNode.Parse |> GymAttendance.jsonParse |> shouldEqual expected
|
||||
|
||||
let memberActivityDtoCases =
|
||||
let json =
|
||||
"""{"totalDuration":2217,"averageDuration":48,"totalVisits":46,"totalClasses":0,"isEstimated":false,"lastRefreshed":"2023-12-27T19:00:56.0309892Z"}"""
|
||||
|
||||
let value =
|
||||
{
|
||||
TotalDuration = 2217
|
||||
AverageDuration = 48
|
||||
TotalVisits = 46
|
||||
TotalClasses = 0
|
||||
IsEstimated = false
|
||||
LastRefreshed =
|
||||
DateTime (2023, 12, 27, 19, 00, 56, 030, 989, DateTimeKind.Utc)
|
||||
+ TimeSpan.FromTicks 2L
|
||||
}
|
||||
|
||||
[ json, value ] |> List.map TestCaseData
|
||||
|
||||
[<TestCaseSource(nameof memberActivityDtoCases)>]
|
||||
let ``MemberActivityDto JSON parse`` (json : string, expected : MemberActivityDto) =
|
||||
json |> JsonNode.Parse |> MemberActivityDto.jsonParse |> shouldEqual expected
|
||||
|
||||
let sessionsCases =
|
||||
let json =
|
||||
"""{
|
||||
"Summary":{"Total":{"Activities":0,"Visits":10,"Duration":445},"ThisWeek":{"Activities":0,"Visits":0,"Duration":0}},
|
||||
"Visits":[
|
||||
{"IsDurationEstimated":false,"Gym":{"Id":19,"Name":"London Oval","Status":"Blocked","Location":null,"GymAccess":null,"ContactInfo":null,"TimeZone":null},"StartTime":"2023-12-21T10:12:00","Duration":50,"Name":null},
|
||||
{"IsDurationEstimated":false,"Gym":{"Id":19,"Name":"London Oval","Status":"Blocked","Location":null,"GymAccess":null,"ContactInfo":null,"TimeZone":null},"StartTime":"2023-12-20T12:05:00","Duration":80,"Name":null},
|
||||
{"IsDurationEstimated":false,"Gym":{"Id":19,"Name":"London Oval","Status":"Blocked","Location":null,"GymAccess":null,"ContactInfo":null,"TimeZone":null},"StartTime":"2023-12-17T19:37:00","Duration":46,"Name":null},
|
||||
{"IsDurationEstimated":false,"Gym":{"Id":19,"Name":"London Oval","Status":"Blocked","Location":null,"GymAccess":null,"ContactInfo":null,"TimeZone":null},"StartTime":"2023-12-16T12:19:00","Duration":37,"Name":null},
|
||||
{"IsDurationEstimated":false,"Gym":{"Id":19,"Name":"London Oval","Status":"Blocked","Location":null,"GymAccess":null,"ContactInfo":null,"TimeZone":null},"StartTime":"2023-12-15T11:14:00","Duration":47,"Name":null},
|
||||
{"IsDurationEstimated":false,"Gym":{"Id":19,"Name":"London Oval","Status":"Blocked","Location":null,"GymAccess":null,"ContactInfo":null,"TimeZone":null},"StartTime":"2023-12-13T10:30:00","Duration":36,"Name":null},
|
||||
{"IsDurationEstimated":false,"Gym":{"Id":19,"Name":"London Oval","Status":"Blocked","Location":null,"GymAccess":null,"ContactInfo":null,"TimeZone":null},"StartTime":"2023-12-10T16:18:00","Duration":32,"Name":null},
|
||||
{"IsDurationEstimated":false,"Gym":{"Id":19,"Name":"London Oval","Status":"Blocked","Location":null,"GymAccess":null,"ContactInfo":null,"TimeZone":null},"StartTime":"2023-12-05T22:36:00","Duration":40,"Name":null},
|
||||
{"IsDurationEstimated":false,"Gym":{"Id":19,"Name":"London Oval","Status":"Blocked","Location":null,"GymAccess":null,"ContactInfo":null,"TimeZone":null},"StartTime":"2023-12-03T17:59:00","Duration":48,"Name":null},
|
||||
{"IsDurationEstimated":false,"Gym":{"Id":19,"Name":"London Oval","Status":"Blocked","Location":null,"GymAccess":null,"ContactInfo":null,"TimeZone":null},"StartTime":"2023-12-01T21:41:00","Duration":29,"Name":null}],
|
||||
"Activities":[]}
|
||||
"""
|
||||
|
||||
let singleVisit startTime duration =
|
||||
{
|
||||
IsDurationEstimated = false
|
||||
Gym =
|
||||
{
|
||||
Id = 19
|
||||
Name = "London Oval"
|
||||
Status = "Blocked"
|
||||
}
|
||||
StartTime = startTime
|
||||
Duration = duration
|
||||
}
|
||||
|
||||
let expected =
|
||||
{
|
||||
Summary =
|
||||
{
|
||||
Total =
|
||||
{
|
||||
Activities = 0
|
||||
Visits = 10
|
||||
Duration = 445
|
||||
}
|
||||
ThisWeek =
|
||||
{
|
||||
Activities = 0
|
||||
Visits = 0
|
||||
Duration = 0
|
||||
}
|
||||
}
|
||||
Visits =
|
||||
[
|
||||
singleVisit (DateTime (2023, 12, 21, 10, 12, 00)) 50
|
||||
singleVisit (DateTime (2023, 12, 20, 12, 05, 00)) 80
|
||||
singleVisit (DateTime (2023, 12, 17, 19, 37, 00)) 46
|
||||
singleVisit (DateTime (2023, 12, 16, 12, 19, 00)) 37
|
||||
singleVisit (DateTime (2023, 12, 15, 11, 14, 00)) 47
|
||||
singleVisit (DateTime (2023, 12, 13, 10, 30, 00)) 36
|
||||
singleVisit (DateTime (2023, 12, 10, 16, 18, 00)) 32
|
||||
singleVisit (DateTime (2023, 12, 05, 22, 36, 00)) 40
|
||||
singleVisit (DateTime (2023, 12, 03, 17, 59, 00)) 48
|
||||
singleVisit (DateTime (2023, 12, 01, 21, 41, 00)) 29
|
||||
]
|
||||
}
|
||||
|
||||
[ json, expected ] |> List.map TestCaseData
|
||||
|
||||
[<TestCaseSource(nameof sessionsCases)>]
|
||||
let ``Sessions JSON parse`` (json : string, expected : Sessions) =
|
||||
json
|
||||
|> fun o -> JsonNode.Parse (o, Nullable (JsonNodeOptions (PropertyNameCaseInsensitive = true)))
|
||||
|> Sessions.jsonParse
|
||||
|> shouldEqual expected
|
Reference in New Issue
Block a user