taler-ios

iOS apps for GNU Taler (wallet)
Log | Files | Refs | README | LICENSE

TimeTests.swift (8089B)


      1 /*
      2  * This file is part of GNU Taler, ©2022-23 Taler Systems S.A.
      3  * See LICENSE.md
      4  */
      5 import XCTest
      6 @testable import taler_swift
      7 
      8 class TimeTests: XCTestCase {
      9     func testParsing() {
     10         // The protocol wire format is TalerProtocolTimestamp: {"t_s": <seconds>} with the
     11         // string sentinel "never". The legacy {"t_ms": ...} form is still accepted for
     12         // numbers only (see testParsingLegacyMilliseconds).
     13         let json1 = "{ \"t_s\" : 12309 }".data(using: .utf8)!
     14         let json2 = "{ \"t_s\" : \"never\" }".data(using: .utf8)!
     15         let json3 = "{ \"t_s\" : \"sometime\" }".data(using: .utf8)!
     16 
     17         var t: Timestamp = try! JSONDecoder().decode(Timestamp.self, from: json1)
     18         XCTAssertEqual(t, Timestamp.milliseconds(12309 * 1000))
     19 
     20         t = try! JSONDecoder().decode(Timestamp.self, from: json2)
     21         XCTAssertEqual(t, Timestamp.never)
     22         XCTAssertThrowsError(t = try JSONDecoder().decode(Timestamp.self, from: json3))
     23     }
     24 
     25     func testParsingLegacyMilliseconds() {
     26         // A numeric legacy t_ms still decodes...
     27         let json1 = "{ \"t_ms\" : 12309487 }".data(using: .utf8)!
     28         let t: Timestamp = try! JSONDecoder().decode(Timestamp.self, from: json1)
     29         XCTAssertEqual(t, Timestamp.milliseconds(12309487))
     30         // ...but "never" under the legacy key does NOT: the fallback in
     31         // Timestamp.init(from:) only ever looks for the string sentinel under `t_s`.
     32         // Documented here as current behaviour, not endorsed — see bugs.txt.
     33         let json2 = "{ \"t_ms\" : \"never\" }".data(using: .utf8)!
     34         XCTAssertThrowsError(try JSONDecoder().decode(Timestamp.self, from: json2))
     35     }
     36 
     37     func testParsingOutOfRangeSeconds() {
     38         // The protocol caps a timestamp at 2^53-1 seconds, which must still decode...
     39         let maxSeconds: UInt64 = (1 << 53) - 1
     40         let json1 = "{ \"t_s\" : \(maxSeconds) }".data(using: .utf8)!
     41         let t: Timestamp = try! JSONDecoder().decode(Timestamp.self, from: json1)
     42         XCTAssertEqual(t, Timestamp.milliseconds(maxSeconds * 1000))
     43 
     44         // ...while anything above it must be rejected. Multiplying it by 1000 unchecked
     45         // overflows UInt64 and traps, which no do/catch around the decode can survive.
     46         for tooBig in [maxSeconds + 1, 20_000_000_000_000_000, UInt64.max] {
     47             let json = "{ \"t_s\" : \(tooBig) }".data(using: .utf8)!
     48             XCTAssertThrowsError(try JSONDecoder().decode(Timestamp.self, from: json))
     49         }
     50     }
     51 
     52     func testSerialize() {
     53         // Encoding is lossy: milliseconds are truncated to whole seconds (see bugs.txt),
     54         // so a round trip only holds for whole-second timestamps.
     55         let str1 = "{\"t_s\":12309}"
     56         let time1 = Timestamp.milliseconds(12309 * 1000)
     57 
     58         let str2 = "{\"t_s\":\"never\"}"
     59         let time2 = Timestamp.never
     60         
     61         XCTAssert(try! String(data: JSONEncoder().encode(time1), encoding: .utf8)! == str1)
     62         XCTAssert(try! String(data: JSONEncoder().encode(time2), encoding: .utf8)! == str2)
     63     }
     64     
     65     func testTimestampDurationAdd() {
     66         XCTAssert(Timestamp.never + Duration.microseconds((0) * 1000) == Timestamp.never)
     67         XCTAssert(Timestamp.never + Duration.microseconds((123456) * 1000) == Timestamp.never)
     68         XCTAssert(Timestamp.never + Duration.forever == Timestamp.never)
     69         
     70         XCTAssert(Timestamp.milliseconds(0) + Duration.microseconds((0) * 1000) == Timestamp.milliseconds(0))
     71         XCTAssert(Timestamp.milliseconds(0) + Duration.microseconds((12309487) * 1000) == Timestamp.milliseconds(12309487))
     72         XCTAssert(Timestamp.milliseconds(0) + Duration.forever == Timestamp.never)
     73         
     74         XCTAssert(Timestamp.milliseconds(12309487) + Duration.microseconds((0) * 1000) == Timestamp.milliseconds(12309487))
     75         // Duration is microseconds, Timestamp is milliseconds, and `+` divides the addend
     76         // by 1000. So the largest addend any Duration can express is UInt64.max/1000 ms —
     77         // which means the overflow boundary now sits near the top of the *Timestamp* range,
     78         // not near the addend. Test the exact boundary and one millisecond past it.
     79         let maxAddendMs = UInt64.max / 1000
     80         XCTAssert(Timestamp.milliseconds(UInt64.max - maxAddendMs) + Duration.microseconds(UInt64.max) == Timestamp.milliseconds(UInt64.max))
     81         XCTAssert(Timestamp.milliseconds(UInt64.max - maxAddendMs + 1) + Duration.microseconds(UInt64.max) == Timestamp.never)
     82         XCTAssert(Timestamp.milliseconds(12309487) + Duration.forever == Timestamp.never)
     83     }
     84     
     85     func testTimestampDurationSubtraction() {
     86         XCTAssert(Timestamp.never - Duration.microseconds((0) * 1000) == Timestamp.never)
     87         XCTAssert(Timestamp.never - Duration.microseconds((123456) * 1000) == Timestamp.never)
     88         XCTAssert(Timestamp.never - Duration.forever == Timestamp.never)
     89         
     90         XCTAssert(Timestamp.milliseconds(0) - Duration.microseconds((0) * 1000) == Timestamp.milliseconds(0))
     91         XCTAssert(Timestamp.milliseconds(0) - Duration.microseconds((123456) * 1000) == Timestamp.milliseconds(0))
     92         XCTAssert(Timestamp.milliseconds(0) - Duration.forever == Timestamp.milliseconds(0))
     93         
     94         XCTAssert(Timestamp.milliseconds(12309487) - Duration.microseconds((0) * 1000) == Timestamp.milliseconds(12309487))
     95         XCTAssert(Timestamp.milliseconds(12309487) - Duration.microseconds((487) * 1000) == Timestamp.milliseconds(12309000))
     96         XCTAssert(Timestamp.milliseconds(12309487) - Duration.microseconds((12309487) * 1000) == Timestamp.milliseconds(0))
     97         XCTAssert(Timestamp.milliseconds(12309487) - Duration.microseconds((12309488) * 1000) == Timestamp.milliseconds(0))
     98         XCTAssert(Timestamp.milliseconds(12309487) - Duration.forever == Timestamp.milliseconds(0))
     99     }
    100     
    101     func testTimestampDifference() {
    102         XCTAssert(try! Timestamp.never - Timestamp.milliseconds(0) == Duration.forever)
    103         XCTAssert(try! Timestamp.never - Timestamp.milliseconds(123456) == Duration.forever)
    104         XCTAssert(try! Timestamp.never - Timestamp.never == Duration.forever)
    105         
    106         XCTAssert(try! Timestamp.milliseconds(0) - Timestamp.milliseconds(0) == Duration.microseconds((0) * 1000))
    107         XCTAssert(try! Timestamp.milliseconds(0) - Timestamp.milliseconds(123456) == Duration.microseconds((0) * 1000))
    108         XCTAssertThrowsError(try Timestamp.milliseconds(0) - Timestamp.never)
    109         
    110         XCTAssert(try! Timestamp.milliseconds(12309487) - Timestamp.milliseconds(0) == Duration.microseconds((12309487) * 1000))
    111         XCTAssert(try! Timestamp.milliseconds(12309487) - Timestamp.milliseconds(12309000) == Duration.microseconds((487) * 1000))
    112         XCTAssert(try! Timestamp.milliseconds(12309487) - Timestamp.milliseconds(12309487) == Duration.microseconds((0) * 1000))
    113         XCTAssert(try! Timestamp.milliseconds(12309487) - Timestamp.milliseconds(12309488) == Duration.microseconds((0) * 1000))
    114         XCTAssertThrowsError(try Timestamp.milliseconds(12309487) - Timestamp.never)
    115     }
    116 
    117     func testTimestampDifferenceOverflow() {
    118         // t_s = 2^53-1 is a timestamp the protocol allows, so it can arrive from any
    119         // exchange or merchant. Its distance from now doesn't fit into microseconds:
    120         // the difference must saturate to forever, not trap.
    121         let maxSeconds: UInt64 = (1 << 53) - 1
    122         let farFuture = Timestamp.milliseconds(maxSeconds * 1000)
    123         XCTAssert(try! farFuture - Timestamp.milliseconds(0) == Duration.forever)
    124         XCTAssert(try! farFuture.duration() == Duration.forever)
    125         XCTAssert(try! Timestamp.milliseconds(UInt64.max) - Timestamp.milliseconds(0) == Duration.forever)
    126 
    127         // right below the boundary the exact difference is still returned
    128         let maxDiffMs = UInt64.max / 1000
    129         XCTAssert(try! Timestamp.milliseconds(maxDiffMs) - Timestamp.milliseconds(0) == Duration.microseconds(maxDiffMs * 1000))
    130         XCTAssert(try! Timestamp.milliseconds(maxDiffMs + 1) - Timestamp.milliseconds(0) == Duration.forever)
    131     }
    132 }