| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134 |
- package aws
- import (
- "context"
- "fmt"
- "testing"
- "time"
- awsSDK "github.com/aws/aws-sdk-go-v2/aws"
- "github.com/aws/aws-sdk-go-v2/service/ec2"
- ec2Types "github.com/aws/aws-sdk-go-v2/service/ec2/types"
- )
- // fakeSpotClient implements spotPriceHistoryClient by returning a fixed set of
- // pages. Each element in pages is one page of results.
- type fakeSpotClient struct {
- pages [][]ec2Types.SpotPrice
- calls int
- err error // if set, returned on every NextPage call
- }
- func (f *fakeSpotClient) DescribeSpotPriceHistory(
- _ context.Context,
- _ *ec2.DescribeSpotPriceHistoryInput,
- _ ...func(*ec2.Options),
- ) (*ec2.DescribeSpotPriceHistoryOutput, error) {
- if f.err != nil {
- return nil, f.err
- }
- if f.calls >= len(f.pages) {
- return &ec2.DescribeSpotPriceHistoryOutput{}, nil
- }
- page := f.pages[f.calls]
- f.calls++
- var nextToken *string
- if f.calls < len(f.pages) {
- nextToken = awsSDK.String("next")
- }
- return &ec2.DescribeSpotPriceHistoryOutput{
- SpotPriceHistory: page,
- NextToken: nextToken,
- }, nil
- }
- func spotItem(instanceType, price string, az string) ec2Types.SpotPrice {
- ts := time.Now()
- return ec2Types.SpotPrice{
- InstanceType: ec2Types.InstanceType(instanceType),
- SpotPrice: awsSDK.String(price),
- Timestamp: &ts,
- AvailabilityZone: awsSDK.String(az),
- }
- }
- func TestQuerySpotPrices_Basic(t *testing.T) {
- client := &fakeSpotClient{
- pages: [][]ec2Types.SpotPrice{
- {
- spotItem("m5.large", "0.05", "us-west-2a"),
- spotItem("c5.xlarge", "0.10", "us-west-2b"),
- },
- },
- }
- results, err := querySpotPrices(context.Background(), "us-west-2", client)
- if err != nil {
- t.Fatalf("unexpected error: %v", err)
- }
- if len(results) != 2 {
- t.Fatalf("expected 2 results, got %d", len(results))
- }
- byType := make(map[string]float64)
- for _, r := range results {
- byType[r.InstanceType] = r.Price
- }
- if byType["m5.large"] != 0.05 {
- t.Errorf("m5.large price = %f, want 0.05", byType["m5.large"])
- }
- if byType["c5.xlarge"] != 0.10 {
- t.Errorf("c5.xlarge price = %f, want 0.10", byType["c5.xlarge"])
- }
- }
- func TestQuerySpotPrices_DeduplicatesAcrossAZs(t *testing.T) {
- // Same instance type appears in multiple AZs — only the first should be kept.
- client := &fakeSpotClient{
- pages: [][]ec2Types.SpotPrice{
- {
- spotItem("m5.large", "0.05", "us-west-2a"),
- spotItem("m5.large", "0.09", "us-west-2b"), // duplicate, should be skipped
- },
- },
- }
- results, err := querySpotPrices(context.Background(), "us-west-2", client)
- if err != nil {
- t.Fatalf("unexpected error: %v", err)
- }
- if len(results) != 1 {
- t.Fatalf("expected 1 result after dedup, got %d", len(results))
- }
- if results[0].Price != 0.05 {
- t.Errorf("expected first price 0.05, got %f", results[0].Price)
- }
- }
- func TestQuerySpotPrices_MultiPage(t *testing.T) {
- client := &fakeSpotClient{
- pages: [][]ec2Types.SpotPrice{
- {spotItem("m5.large", "0.05", "us-west-2a")},
- {spotItem("c5.xlarge", "0.10", "us-west-2a")},
- {spotItem("r5.2xlarge", "0.20", "us-west-2a")},
- },
- }
- results, err := querySpotPrices(context.Background(), "us-west-2", client)
- if err != nil {
- t.Fatalf("unexpected error: %v", err)
- }
- if len(results) != 3 {
- t.Fatalf("expected 3 results across pages, got %d", len(results))
- }
- }
- func TestQuerySpotPrices_APIError(t *testing.T) {
- client := &fakeSpotClient{
- err: fmt.Errorf("api unavailable"),
- }
- _, err := querySpotPrices(context.Background(), "us-west-2", client)
- if err == nil {
- t.Fatal("expected error, got nil")
- }
- }
|