Files
ShadowsocksX-NG/ShadowsocksX-NG2/Tests/Subscription/SubscriptionInformationParserTests.swift

193 lines
7.6 KiB
Swift

import Foundation
import Testing
@testable import ShadowsocksX_NG2
struct SubscriptionInformationParserTests {
@Test func subscriptionInformationUsesStandardRootAndNamespacedDateFields() throws {
let snapshot = try SubscriptionDocumentParser.parse(
Data(
#"""
{"version":1,
"servers":[],
"bytes_used":25,
"bytes_remaining":75,
"x_shadowsocksx_ng":{"schema_version":1,
"expires_at":"2026-10-04T08:30:00+08:00",
"traffic_reset_at":"2026-10-05T00:00:00.125Z"}}
"""#
.utf8),
subscriptionID: .fresh())
#expect(snapshot.information.bytesUsed == 25)
#expect(snapshot.information.bytesRemaining == 75)
#expect(snapshot.information.expiresAt == Date(timeIntervalSince1970: 1_791_073_800))
#expect(snapshot.information.trafficResetAt == Date(timeIntervalSince1970: 1_791_158_400.125))
}
@Test func rfc3339OffsetOutsideTimeZoneFactoryRangeIsAccepted() throws {
let snapshot = try SubscriptionDocumentParser.parse(
Data(
#"{"version":1,"servers":[],"x_shadowsocksx_ng":{"schema_version":1,"expires_at":"2026-10-04T23:59:00+23:59"}}"#
.utf8),
subscriptionID: .fresh())
#expect(snapshot.information.expiresAt == Date(timeIntervalSince1970: 1_791_072_000))
}
@Test(arguments: [
"true", "false", "\"25\"", "-1", "1.5", "1.0", "1e3", "1e999", "18446744073709551616", "null",
"[]",
"{}",
])
func invalidByteCountsDoNotDiscardValidInformation(_ literal: String) throws {
let snapshot = try parse(
#"""
"bytes_used":\#(literal),
"bytes_remaining":75,
"x_shadowsocksx_ng":{"schema_version":1,
"expires_at":"1970-01-01T00:00:00Z"}
"""#
)
#expect(snapshot.information.bytesUsed == nil)
#expect(snapshot.information.bytesRemaining == 75)
#expect(snapshot.information.expiresAt == Date(timeIntervalSince1970: 0))
}
@Test func fullUnsignedRangeAndZeroAreAccepted() throws {
let snapshot = try parse(#""bytes_used":18446744073709551615,"bytes_remaining":0"#)
#expect(snapshot.information.bytesUsed == UInt64.max)
#expect(snapshot.information.bytesRemaining == 0)
}
@Test(arguments: [
#""2026-02-30T12:30:00Z""#, #""2026-10-04T24:00:00Z""#,
#""2026-10-04T00:00:00""#, #""2026-10-04T00:00:00Zjunk""#,
#""2026-10-04""#, #""2026-10-04T23:59:60Z""#, #""not a date""#,
"1791072000", "null", "true", "{}", "[]",
])
func invalidDateDoesNotAffectOtherFields(_ literal: String) throws {
let snapshot = try parse(
#"""
"bytes_used":0,
"x_shadowsocksx_ng":{"schema_version":1,
"expires_at":\#(literal),
"traffic_reset_at":"2026-10-04T00:00:00Z"}
"""#
)
#expect(snapshot.information.expiresAt == nil)
#expect(snapshot.information.trafficResetAt == Date(timeIntervalSince1970: 1_791_072_000))
#expect(snapshot.information.bytesUsed == 0)
}
@Test func rfc3339CaseOffsetsAndLeapSecondPreserveInstants() throws {
let snapshot = try parse(
#"""
"x_shadowsocksx_ng":{"schema_version":1,
"expires_at":"2026-10-03t19:00:00-05:00",
"traffic_reset_at":"2016-12-31T23:59:60Z"}
"""#
)
#expect(snapshot.information.expiresAt == Date(timeIntervalSince1970: 1_791_072_000))
#expect(snapshot.information.trafficResetAt == Date(timeIntervalSince1970: 1_483_228_800))
}
@Test func informationAndGroupingValidityAreIndependent() throws {
let treeText = try #require(String(bytes: SubscriptionDocs.tree(), encoding: .utf8))
.trimmingCharacters(in: .whitespacesAndNewlines)
let validTree = try SubscriptionDocumentParser.parse(
Data(
(treeText.replacingOccurrences(
of: "\"schema_version\": 1", with: "\"schema_version\": 1,\"expires_at\":123"
).dropLast() + #","bytes_used":25}"#).utf8),
subscriptionID: .fresh())
#expect(validTree.root.name == "Example subscription")
#expect(validTree.information.bytesUsed == 25)
#expect(validTree.information.expiresAt == nil)
let invalidTree = try parse(#""bytes_remaining":75,"x_shadowsocksx_ng":{"schema_version":99}"#)
#expect(invalidTree.root.name.isEmpty)
#expect(invalidTree.information.bytesRemaining == 75)
}
@Test func onlyExpectedPathsAreReadAcrossNestedEscapedAndUnrepresentableValues() throws {
let snapshot = try SubscriptionDocumentParser.parse(
Data(
#"""
{"version":1,"servers":[{"server":"203.0.113.7","server_port":8388,
"password":"{\"bytes_used\":999},\\tail","method":"aes-256-gcm","bytes_remaining":999}],
"ignored":1e999,"other":{"expires_at":"1970-01-01T00:00:00Z"},
"bytes_\u0075sed":25,"bytes_remaining":75,"expires_at":"1970-01-01T00:00:00Z",
"x_shadowsocksx_ng":{"schema_version":1,"ignored":1e999,"bytes_used":999,
"expires_\u0061t":"2026-10-04T00:00:00Z","other":{"traffic_reset_at":"1970-01-01T00:00:00Z"}}}
"""#.utf8), subscriptionID: .fresh())
#expect(snapshot.information.bytesUsed == 25)
#expect(snapshot.information.bytesRemaining == 75)
#expect(snapshot.information.expiresAt == Date(timeIntervalSince1970: 1_791_072_000))
#expect(snapshot.information.trafficResetAt == nil)
#expect(snapshot.root.children.count == 1)
}
@Test func utf8BOMPreservesValidSubscriptionInformation() throws {
let data =
Data([0xEF, 0xBB, 0xBF])
+ Data(#"{"version":1,"servers":[],"bytes_used":25,"bytes_remaining":75}"#.utf8)
let snapshot = try SubscriptionDocumentParser.parse(data, subscriptionID: .fresh())
#expect(snapshot.information.bytesUsed == 25)
#expect(snapshot.information.bytesRemaining == 75)
}
@Test(arguments: [
"null", "[]", "true", "1", #""text""#, "{}",
#"{"schema_version":99}"#, #"{"schema_version":null}"#,
#"{"schema_version":true}"#, #"{"schema_version":"1"}"#,
#"{"schema_version":1e999}"#,
#"{"expires_at":"1970-01-01T00:00:00Z"}"#,
#"{"schema_version":99,"expires_at":"1970-01-01T00:00:00Z"}"#,
])
func invalidNamespaceOrVersionDoesNotReadDatesOrDiscardTraffic(_ literal: String) throws {
let snapshot = try parse(
#"""
"bytes_used":25,
"bytes_remaining":75,
"expires_at":"1970-01-01T00:00:00Z",
"traffic_reset_at":"1970-01-01T00:00:00Z",
"x_shadowsocksx_ng":\#(literal)
"""#
)
#expect(snapshot.information.bytesUsed == 25)
#expect(snapshot.information.bytesRemaining == 75)
#expect(snapshot.information.expiresAt == nil)
#expect(snapshot.information.trafficResetAt == nil)
}
@Test func rootDatesAreIgnoredWithoutNamespaceFallback() throws {
let snapshot = try parse(
#""expires_at":"1970-01-01T00:00:00Z","traffic_reset_at":"1970-01-01T00:00:00Z""#)
#expect(snapshot.information.isEmpty)
let invalidDate = try parse(
#""expires_at":"1970-01-01T00:00:00Z","x_shadowsocksx_ng":{"schema_version":1,"expires_at":null}"#
)
#expect(invalidDate.information.isEmpty)
}
@Test func invalidTreeAndPolicyDoNotDiscardValidDates() throws {
let snapshot = try parse(
#"""
"x_shadowsocksx_ng":{"schema_version":1,
"groups":false,
"auto_update":false,
"expires_at":"1970-01-01T00:00:00Z",
"traffic_reset_at":"1970-01-02T00:00:00Z"}
"""#
)
#expect(snapshot.information.expiresAt == Date(timeIntervalSince1970: 0))
#expect(snapshot.information.trafficResetAt == Date(timeIntervalSince1970: 86_400))
#expect(snapshot.root.children.isEmpty)
#expect(snapshot.autoUpdatePolicy == .invalid)
}
private func parse(_ fields: String) throws -> SubscriptionSnapshot {
try SubscriptionDocumentParser.parse(
Data("{\"version\":1,\"servers\":[],\(fields)}".utf8), subscriptionID: .fresh())
}
}