Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
namespace HeimdallPower.Api.Client.UnitTests.WhenHandlingTransientErrors.Fakes;

/// <summary>
/// A minimal <see cref="HttpMessageHandler"/> for unit tests that returns
/// a fixed response or a pre-configured sequence of responses.
/// </summary>
internal sealed class FakeHttpMessageHandler : HttpMessageHandler
{
private readonly Queue<HttpResponseMessage> _responses;
private readonly HttpResponseMessage _last;

/// <summary>Returns <paramref name="response"/> for every request.</summary>
public FakeHttpMessageHandler(HttpResponseMessage response)
{
_responses = new Queue<HttpResponseMessage>();
_last = response;
}

/// <summary>
/// Returns responses from <paramref name="responses"/> in order.
/// Once the queue is exhausted the last response is repeated.
/// </summary>
public FakeHttpMessageHandler(params HttpResponseMessage[] responses)
{
if (responses.Length == 0) throw new ArgumentException("At least one response is required.", nameof(responses));
_last = responses[^1];
_responses = new Queue<HttpResponseMessage>(responses[..^1]);
}

protected override Task<HttpResponseMessage> SendAsync(
HttpRequestMessage request,
CancellationToken cancellationToken)
{
var response = _responses.Count > 0 ? _responses.Dequeue() : _last;
response.RequestMessage = request;
return Task.FromResult(response);
}
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
using System.Net;

namespace HeimdallPower.Api.Client.UnitTests.WhenHandlingTransientErrors.Fakes;

internal static class HeimdallApiHttpClientFactory
{
/// <summary>
/// Creates a <see cref="HeimdallApiHttpClient"/> backed by <paramref name="handler"/>.
/// The delay function is a no-op so tests complete instantly.
/// </summary>
public static HeimdallApiHttpClient Create(FakeHttpMessageHandler handler)
{
var httpClient = new HttpClient(handler) { BaseAddress = new Uri("https://fake.heimdallcloud.com") };
return new HeimdallApiHttpClient(
accessTokenProvider: new StubAccessTokenProvider(),
httpClient: httpClient,
clientMetadata: null); // no real sleeping in unit tests
}

/// <summary>Convenience: single repeating response.</summary>
public static HeimdallApiHttpClient CreateWithFixedResponse(HttpResponseMessage response)
=> Create(new FakeHttpMessageHandler(response));

/// <summary>Convenience: a sequence of responses (e.g. N failures then a success).</summary>
public static HeimdallApiHttpClient CreateWithSequence(params HttpResponseMessage[] responses)
=> Create(new FakeHttpMessageHandler(responses));
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
namespace HeimdallPower.Api.Client.UnitTests.WhenHandlingTransientErrors.Fakes;

/// <summary>
/// A no-op token provider — tests only exercise the HTTP retry layer,
/// so the token is pre-set and never expired.
/// </summary>
internal sealed class StubAccessTokenProvider : IAccessTokenProvider
{
public Task AcquireTokenAsync(CancellationToken cancellationToken = default) => Task.CompletedTask;

public DateTimeOffset GetTokenExpiry()
=> DateTimeOffset.UtcNow.AddHours(1); // always valid

public IDictionary<string, string> GetAccessHeaders()
=> new Dictionary<string, string>
{
{ "Authorization", "Bearer stub-token" }
};
}

92 changes: 92 additions & 0 deletions python/heimdall_api_client/capacity_monitoring.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
from __future__ import annotations

import datetime
from typing import TYPE_CHECKING
from uuid import UUID

Expand All @@ -19,12 +20,21 @@
from heimdall_api_client.errors import HeimdallApiError, body_preview

if TYPE_CHECKING:
from heimdall_api_client.capacity_monitoring_api_client.models.capacity_monitoring_v1_facilities_get_circuit_ratings_response_200 import ( # noqa: E501
CapacityMonitoringV1FacilitiesGetCircuitRatingsResponse200,
)
from heimdall_api_client.capacity_monitoring_api_client.models.capacity_monitoring_v1_facilities_get_latest_circuit_rating_forecasts_response_200 import ( # noqa: E501
CapacityMonitoringV1FacilitiesGetLatestCircuitRatingForecastsResponse200,
)
from heimdall_api_client.capacity_monitoring_api_client.models.capacity_monitoring_v1_facilities_get_latest_circuit_rating_response_200 import ( # noqa: E501
CapacityMonitoringV1FacilitiesGetLatestCircuitRatingResponse200,
)
from heimdall_api_client.capacity_monitoring_api_client.models.capacity_monitoring_v1_lines_get_heimdall_aars_response_200 import ( # noqa: E501
CapacityMonitoringV1LinesGetHeimdallAarsResponse200,
)
from heimdall_api_client.capacity_monitoring_api_client.models.capacity_monitoring_v1_lines_get_heimdall_dlrs_response_200 import ( # noqa: E501
CapacityMonitoringV1LinesGetHeimdallDlrsResponse200,
)
from heimdall_api_client.capacity_monitoring_api_client.models.capacity_monitoring_v1_lines_get_latest_heimdall_aar_forecasts_response_200 import ( # noqa: E501
CapacityMonitoringV1LinesGetLatestHeimdallAarForecastsResponse200,
)
Expand Down Expand Up @@ -131,3 +141,85 @@ def get_latest_circuit_rating_forecasts(
status_code=status,
)
return response.parsed


def get_heimdall_dlrs(
client: AuthenticatedClient,
line_id: UUID,
region: str,
from_timestamp: datetime.datetime,
to_timestamp: datetime.datetime,
) -> CapacityMonitoringV1LinesGetHeimdallDlrsResponse200:
from heimdall_api_client.capacity_monitoring_api_client.api.line import (
capacity_monitoring_v1_lines_get_heimdall_dlrs as _get_heimdall_dlrs,
)

response = _get_heimdall_dlrs.sync_detailed(
client=client,
line_id=line_id,
x_region=region,
from_timestamp=from_timestamp,
to_timestamp=to_timestamp,
)
if response.status_code != 200:
status = int(response.status_code)
raise HeimdallApiError(
f"Error fetching Heimdall DLRs: {status} {response.status_code.phrase} - {body_preview(response.content)}",
status_code=status,
)
return response.parsed


def get_heimdall_aars(
client: AuthenticatedClient,
line_id: UUID,
region: str,
from_timestamp: datetime.datetime,
to_timestamp: datetime.datetime,
) -> CapacityMonitoringV1LinesGetHeimdallAarsResponse200:
from heimdall_api_client.capacity_monitoring_api_client.api.line import (
capacity_monitoring_v1_lines_get_heimdall_aars as _get_heimdall_aars,
)

response = _get_heimdall_aars.sync_detailed(
client=client,
line_id=line_id,
x_region=region,
from_timestamp=from_timestamp,
to_timestamp=to_timestamp,
)
if response.status_code != 200:
status = int(response.status_code)
raise HeimdallApiError(
f"Error fetching Heimdall AARs: {status} {response.status_code.phrase} - {body_preview(response.content)}",
status_code=status,
)
return response.parsed


def get_circuit_ratings(
client: AuthenticatedClient,
facility_id: UUID,
region: str,
from_timestamp: datetime.datetime,
to_timestamp: datetime.datetime,
) -> CapacityMonitoringV1FacilitiesGetCircuitRatingsResponse200:
from heimdall_api_client.capacity_monitoring_api_client.api.facility import (
capacity_monitoring_v1_facilities_get_circuit_ratings as _get_circuit_ratings,
)

response = _get_circuit_ratings.sync_detailed(
client=client,
facility_id=facility_id,
x_region=region,
from_timestamp=from_timestamp,
to_timestamp=to_timestamp,
)
if response.status_code != 200:
status = int(response.status_code)
raise HeimdallApiError(
f"Error fetching circuit ratings: {status} {response.status_code.phrase}"
f" - {body_preview(response.content)}",
status_code=status,
)
return response.parsed
Loading
Loading