Skip to content

Commit

Permalink
Merge pull request #14 from LLCFreedom-Space/refactor-app-health-checks
Browse files Browse the repository at this point in the history
Refactor
  • Loading branch information
gulivero1773 authored Feb 22, 2024
2 parents 377bcf0 + e0ee1da commit 7e7e464
Show file tree
Hide file tree
Showing 19 changed files with 135 additions and 65 deletions.
8 changes: 4 additions & 4 deletions Package.resolved
Original file line number Diff line number Diff line change
Expand Up @@ -68,17 +68,17 @@
"kind" : "remoteSourceControl",
"location" : "https://github.com/vapor/postgres-kit.git",
"state" : {
"revision" : "80ab7737dac4fccd4a8ad38743828dcb71ba7ac8",
"version" : "2.12.2"
"revision" : "475bf6f04ee1840917a70c32b48e4a724df4ccaf",
"version" : "2.12.3"
}
},
{
"identity" : "postgres-nio",
"kind" : "remoteSourceControl",
"location" : "https://github.com/vapor/postgres-nio.git",
"state" : {
"revision" : "fa3137d39bca84843739db1c5a3db2d7f4ae65e6",
"version" : "1.20.0"
"revision" : "69ccfdf4c80144d845e3b439961b7ec6cd7ae33f",
"version" : "1.20.2"
}
},
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,12 @@

import Vapor

/// Groups func for get application health check
/// Groups functions for getting application health checks.
public protocol ApplicationChecksProtocol {
/// Get uptime of application
/// - Returns: `HealthCheckItem`
///
/// This method provides a basic indication of the application's system-level health.
/// Concrete implementations may extend this to provide more granular or platform-specific checks.
/// - Returns: A `HealthCheckItem` representing the application's uptime in seconds.
func uptime() -> HealthCheckItem
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,15 +24,17 @@

import Vapor

/// Service that provides app health check functionality
/// Service that provides functionality to check the health of an application.
///
/// This struct implements the `ApplicationHealthChecksProtocol`, allowing you to perform various checks on your application's health.
public struct ApplicationHealthChecks: ApplicationHealthChecksProtocol {
/// Instance of app as `Application`
/// Reference to the application instance.
public let app: Application

/// Get uptime of system
/// - Returns: `HealthCheckItem`
/// Get uptime of the system.
/// - Returns: A `HealthCheckItem` representing the application's uptime.
public func uptime() -> HealthCheckItem {
let uptime = Date().timeIntervalSinceReferenceDate - app.launchTime
let uptime = Date().timeIntervalSinceReferenceDate - app.launchTime
return HealthCheckItem(
componentType: .system,
observedValue: uptime,
Expand All @@ -41,11 +43,11 @@ public struct ApplicationHealthChecks: ApplicationHealthChecksProtocol {
time: app.dateTimeISOFormat.string(from: Date())
)
}

/// Check with setup options
/// - Parameter options: array of `MeasurementType`
/// - Returns: dictionary `[String: HealthCheckItem]`
public func checkHealth(for options: [MeasurementType]) async -> [String: HealthCheckItem] {
/// Performs health checks based on the specified `MeasurementType` options.
/// - Parameter options: An array of `MeasurementType` values indicating which checks to perform.
/// - Returns: A dictionary mapping each `MeasurementType` to a corresponding `HealthCheckItem` representing the result of the check.
public func check(for options: [MeasurementType]) async -> [String: HealthCheckItem] {
var result = ["": HealthCheckItem()]
let measurementTypes = Array(Set(options))
for type in measurementTypes {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,5 +24,5 @@

import Vapor

/// Groups func for get application health check
/// Protocol for checking application health.
public protocol ApplicationHealthChecksProtocol: ApplicationChecksProtocol, ChecksProtocol {}
2 changes: 1 addition & 1 deletion Sources/HealthChecks/ChecksProtocol.swift
Original file line number Diff line number Diff line change
Expand Up @@ -29,5 +29,5 @@ public protocol ChecksProtocol {
/// Check with setup options
/// - Parameter options: array of `MeasurementType`
/// - Returns: dictionary `[String: HealthCheckItem]`
func checkHealth(for options: [MeasurementType]) async -> [String: HealthCheckItem]
func check(for options: [MeasurementType]) async -> [String: HealthCheckItem]
}
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ public struct ConsulHealthChecks: ConsulHealthChecksProtocol {
/// - Parameters:
/// - options: array of `MeasurementType`
/// - Returns: dictionary `[String: HealthCheckItem]`
public func checkHealth(for options: [MeasurementType]) async -> [String: HealthCheckItem] {
public func check(for options: [MeasurementType]) async -> [String: HealthCheckItem] {
var dict = ["": HealthCheckItem()]
let measurementTypes = Array(Set(options))
let dateNow = Date().timeIntervalSinceReferenceDate
Expand Down Expand Up @@ -99,7 +99,7 @@ public struct ConsulHealthChecks: ConsulHealthChecksProtocol {
return HealthCheckItem(
componentId: app.consulConfig?.id,
componentType: .component,
observedValue: Date().timeIntervalSinceReferenceDate - start,
observedValue: Date().timeIntervalSinceReferenceDate - start,
observedUnit: "s",
status: response.status == .ok ? .pass : .fail,
time: app.dateTimeISOFormat.string(from: Date()),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,15 +26,15 @@ import Vapor

/// Groups func for get psql health check
public protocol PostgresChecksProtocol {
/// Get Postgresql version
/// Get Postgres connection
/// - Returns: `HealthCheckItem`
func connection() async -> HealthCheckItem

/// Get response time from postgresql
/// Get psql response time
/// - Returns: `HealthCheckItem`
func getResponseTime() async -> HealthCheckItem
func responseTime() async -> HealthCheckItem

/// Get version from postgresql
/// Get psql version
/// - Returns: `String`
func getVersion() async -> String
}
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ public struct PostgresHealthChecks: PostgresHealthChecksProtocol {
/// Instance of app as `Application`
public let app: Application

/// Get postgresql version
/// Get psql version
/// - Returns: `HealthCheckItem`
public func connection() async -> HealthCheckItem {
let dateNow = Date().timeIntervalSinceReferenceDate
Expand All @@ -50,9 +50,9 @@ public struct PostgresHealthChecks: PostgresHealthChecksProtocol {
return result
}

/// Get response time from postgresql
/// Get psql response time
/// - Returns: `HealthCheckItem`
public func getResponseTime() async -> HealthCheckItem {
public func responseTime() async -> HealthCheckItem {
let dateNow = Date().timeIntervalSinceReferenceDate
let versionDescription = await getVersion()
let result = HealthCheckItem(
Expand All @@ -69,7 +69,7 @@ public struct PostgresHealthChecks: PostgresHealthChecksProtocol {
return result
}

/// Get version from postgresql
/// Get psql version
/// - Returns: `String`
public func getVersion() async -> String {
let rows = try? await (app.db(.psql) as? PostgresDatabase)?.simpleQuery("SELECT version()").get()
Expand All @@ -84,13 +84,13 @@ public struct PostgresHealthChecks: PostgresHealthChecksProtocol {
/// Check with setup options
/// - Parameter options: array of `MeasurementType`
/// - Returns: dictionary `[String: HealthCheckItem]`
public func checkHealth(for options: [MeasurementType]) async -> [String: HealthCheckItem] {
public func check(for options: [MeasurementType]) async -> [String: HealthCheckItem] {
var result = ["": HealthCheckItem()]
let measurementTypes = Array(Set(options))
for type in measurementTypes {
switch type {
case .responseTime:
result["\(ComponentName.postgresql):\(MeasurementType.responseTime)"] = await getResponseTime()
result["\(ComponentName.postgresql):\(MeasurementType.responseTime)"] = await responseTime()
case .connections:
result["\(ComponentName.postgresql):\(MeasurementType.connections)"] = await connection()
default:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,9 +32,9 @@ public protocol RedisChecksProtocol {

/// Get response time from redis
/// - Returns: `HealthCheckItem`
func getResponseTime() async -> HealthCheckItem
func responseTime() async -> HealthCheckItem

/// Get pong from redis
/// Get ping from redis
/// - Returns: `String`
func pong() async -> String
func ping() async -> String
}
14 changes: 7 additions & 7 deletions Sources/HealthChecks/RedisHealthChecks/RedisHealthChecks.swift
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ public struct RedisHealthChecks: RedisHealthChecksProtocol {
/// - Returns: `HealthCheckItem`
public func connection() async -> HealthCheckItem {
let dateNow = Date().timeIntervalSinceReferenceDate
let response = await pong()
let response = await ping()
let result = HealthCheckItem(
componentId: app.redisId,
componentType: .datastore,
Expand All @@ -52,9 +52,9 @@ public struct RedisHealthChecks: RedisHealthChecksProtocol {

/// Get response time from redis
/// - Returns: `HealthCheckItem`
public func getResponseTime() async -> HealthCheckItem {
public func responseTime() async -> HealthCheckItem {
let dateNow = Date().timeIntervalSinceReferenceDate
let response = await pong()
let response = await ping()
let result = HealthCheckItem(
componentId: app.redisId,
componentType: .datastore,
Expand All @@ -69,9 +69,9 @@ public struct RedisHealthChecks: RedisHealthChecksProtocol {
return result
}

/// Get pong from redis
/// Get ping from redis
/// - Returns: `String`
public func pong() async -> String {
public func ping() async -> String {
let result = try? await app.redis.ping().get()
var connectionDescription = "ERROR: No connect to Redis database"
if let result, result.lowercased().contains("pong") {
Expand All @@ -83,13 +83,13 @@ public struct RedisHealthChecks: RedisHealthChecksProtocol {
/// Check with setup options
/// - Parameter options: array of `MeasurementType`
/// - Returns: dictionary `[String: HealthCheckItem]`
public func checkHealth(for options: [MeasurementType]) async -> [String: HealthCheckItem] {
public func check(for options: [MeasurementType]) async -> [String: HealthCheckItem] {
var result = ["": HealthCheckItem()]
let measurementTypes = Array(Set(options))
for type in measurementTypes {
switch type {
case .responseTime:
result["\(ComponentName.redis):\(MeasurementType.responseTime)"] = await getResponseTime()
result["\(ComponentName.redis):\(MeasurementType.responseTime)"] = await responseTime()
case .connections:
result["\(ComponentName.redis):\(MeasurementType.connections)"] = await connection()
default:
Expand Down
71 changes: 68 additions & 3 deletions Tests/HealthChecksTests/ApplicationHealthChecksTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -34,14 +34,79 @@ final class ApplicationHealthChecksTests: XCTestCase {
let response = app.applicationHealthChecks?.uptime()
XCTAssertEqual(response, ApplicationHealthChecksMock.healthCheckItem)
}

func testGetHealth() async {

func testUptimeReturnsValidItem() {
let app = Application(.testing)
defer { app.shutdown() }

let healthChecks = ApplicationHealthChecks(app: app)
let item = healthChecks.uptime()

XCTAssertNotNil(item)
XCTAssertEqual(item.componentType, .system)
XCTAssertEqual(item.observedUnit, "s")
XCTAssertEqual(item.status, .pass)

// Assert time is within a reasonable range of actual uptime
let expectedUptime = Date().timeIntervalSinceReferenceDate - app.launchTime
guard let value = item.observedValue else {
return XCTFail()

Check warning on line 53 in Tests/HealthChecksTests/ApplicationHealthChecksTests.swift

View workflow job for this annotation

GitHub Actions / SwiftLint

An XCTFail call should include a description of the assertion (xctfail_message)
}
XCTAssertTrue(abs(value - expectedUptime) < 1.0)
}

func testCheck() async {
let app = Application(.testing)
defer { app.shutdown() }
app.launchTime = Date().timeIntervalSinceReferenceDate
app.applicationHealthChecks = ApplicationHealthChecksMock()
let result = await app.applicationHealthChecks?.checkHealth(for: [MeasurementType.uptime])
let result = await app.applicationHealthChecks?.check(for: [MeasurementType.uptime])
let uptime = result?[MeasurementType.uptime.rawValue]
XCTAssertEqual(uptime, ApplicationHealthChecksMock.healthCheckItem)
}

func testCheckReturnsValidItems() async {
let app = Application(.testing)
defer { app.shutdown() }

let healthChecks = ApplicationHealthChecks(app: app)
let checks = await healthChecks.check(for: [.uptime])

XCTAssertNotNil(checks)
XCTAssertTrue(checks.keys.contains("uptime"))
guard let uptimeItem = checks["uptime"] else {
return XCTFail()

Check warning on line 78 in Tests/HealthChecksTests/ApplicationHealthChecksTests.swift

View workflow job for this annotation

GitHub Actions / SwiftLint

An XCTFail call should include a description of the assertion (xctfail_message)
}
XCTAssertEqual(uptimeItem.componentType, .system)
XCTAssertEqual(uptimeItem.observedUnit, "s")
XCTAssertEqual(uptimeItem.status, .pass)
guard let observedValue = uptimeItem.observedValue else {
return XCTFail()

Check warning on line 84 in Tests/HealthChecksTests/ApplicationHealthChecksTests.swift

View workflow job for this annotation

GitHub Actions / SwiftLint

An XCTFail call should include a description of the assertion (xctfail_message)
}
let expectedUptime = Date().timeIntervalSinceReferenceDate - app.launchTime
XCTAssertTrue(abs(observedValue - expectedUptime) < 1.0)
}

func testCheckHandlesUnsupportedTypes() async {
let app = Application(.testing)
defer { app.shutdown() }

let healthChecks = ApplicationHealthChecks(app: app)
let checks = await healthChecks.check(for: [.connections])
let expectedResult = [
"": HealthCheckItem(
componentId: nil,
componentType: nil,
observedValue: nil,
observedUnit: nil,
status: nil,
affectedEndpoints: nil,
time: nil,
output: nil,
links: nil,
node: nil
)
]
XCTAssertEqual(checks, expectedResult) // Expect empty result, as .memory is not supported
}
}
4 changes: 2 additions & 2 deletions Tests/HealthChecksTests/ConsulHealthChecksTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ import XCTest
@testable import HealthChecks

final class ConsulHealthChecksTests: XCTestCase {
func testCheckHealth() async {
func testHealthCheck() async {
let app = Application(.testing)
defer { app.shutdown() }
app.consulHealthChecks = ConsulHealthChecksMock()
Expand All @@ -38,7 +38,7 @@ final class ConsulHealthChecksTests: XCTestCase {
password: "password"
)
app.consulConfig = consulConfig
let result = await app.consulHealthChecks?.checkHealth(for: [MeasurementType.responseTime, MeasurementType.connections])
let result = await app.consulHealthChecks?.check(for: [MeasurementType.responseTime, MeasurementType.connections])
let responseTimes = result?["\(ComponentName.consul):\(MeasurementType.responseTime)"]
XCTAssertEqual(responseTimes, ConsulHealthChecksMock.healthCheckItem)
XCTAssertEqual(app.consulConfig?.id, consulConfig.id)
Expand Down
2 changes: 1 addition & 1 deletion Tests/HealthChecksTests/HealthChecksTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ final class HealthChecksTests: XCTestCase {
let serviceId = UUID()
let releaseId = "1.0.0"

func testGetMajorVersion() {
func testGetPublicVersion() {
let app = Application(.testing)
defer { app.shutdown() }
let version = HealthChecks().getPublicVersion(from: releaseId)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ public struct ApplicationHealthChecksMock: ApplicationHealthChecksProtocol {
ApplicationHealthChecksMock.healthCheckItem
}

public func checkHealth(for options: [MeasurementType]) async -> [String: HealthCheckItem] {
public func check(for options: [MeasurementType]) async -> [String: HealthCheckItem] {
let result = [
MeasurementType.uptime.rawValue: ApplicationHealthChecksMock.healthCheckItem
]
Expand Down
2 changes: 1 addition & 1 deletion Tests/HealthChecksTests/Mocks/ConsulHealthChecksMock.swift
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ public struct ConsulHealthChecksMock: ConsulHealthChecksProtocol {
node: nil
)

public func checkHealth(for options: [MeasurementType]) async -> [String: HealthCheckItem] {
public func check(for options: [MeasurementType]) async -> [String: HealthCheckItem] {
let result = [
"\(ComponentName.consul):\(MeasurementType.responseTime)": ConsulHealthChecksMock.healthCheckItem,
"\(ComponentName.consul):\(MeasurementType.connections)": ConsulHealthChecksMock.healthCheckItem
Expand Down
4 changes: 2 additions & 2 deletions Tests/HealthChecksTests/Mocks/PostgresHealthChecksMock.swift
Original file line number Diff line number Diff line change
Expand Up @@ -45,15 +45,15 @@ public struct PostgresHealthChecksMock: PostgresHealthChecksProtocol {
PostgresHealthChecksMock.healthCheckItem
}

public func getResponseTime() async -> HealthCheckItem {
public func responseTime() async -> HealthCheckItem {
PostgresHealthChecksMock.healthCheckItem
}

public func getVersion() async -> String {
PostgresHealthChecksMock.version
}

public func checkHealth(for options: [MeasurementType]) async -> [String: HealthCheckItem] {
public func check(for options: [MeasurementType]) async -> [String: HealthCheckItem] {
let result = [
"\(ComponentName.postgresql):\(MeasurementType.responseTime)": PostgresHealthChecksMock.healthCheckItem,
"\(ComponentName.postgresql):\(MeasurementType.connections)": PostgresHealthChecksMock.healthCheckItem
Expand Down
Loading

0 comments on commit 7e7e464

Please sign in to comment.