66 "testing"
77 "time"
88
9+ "github.com/LeanerCloud/CUDly/internal/config"
910 "github.com/aws/aws-lambda-go/events"
1011 "github.com/stretchr/testify/assert"
1112 "github.com/stretchr/testify/mock"
@@ -17,8 +18,8 @@ type MockAnalyticsClient struct {
1718 mock.Mock
1819}
1920
20- func (m * MockAnalyticsClient ) QueryHistory (ctx context.Context , accountID string , start , end time.Time , interval string ) ([]HistoryDataPoint , * HistorySummary , error ) {
21- args := m .Called (ctx , accountID , start , end , interval )
21+ func (m * MockAnalyticsClient ) QueryHistory (ctx context.Context , accountUUIDs , accountExternalIDs [] string , start , end time.Time , interval string ) ([]HistoryDataPoint , * HistorySummary , error ) {
22+ args := m .Called (ctx , accountUUIDs , accountExternalIDs , start , end , interval )
2223 if args .Get (0 ) == nil {
2324 return nil , nil , args .Error (2 )
2425 }
@@ -29,14 +30,41 @@ func (m *MockAnalyticsClient) QueryHistory(ctx context.Context, accountID string
2930 return args .Get (0 ).([]HistoryDataPoint ), summary , args .Error (2 )
3031}
3132
32- func (m * MockAnalyticsClient ) QueryBreakdown (ctx context.Context , accountID string , start , end time.Time , dimension string ) (map [string ]BreakdownValue , error ) {
33- args := m .Called (ctx , accountID , start , end , dimension )
33+ func (m * MockAnalyticsClient ) QueryBreakdown (ctx context.Context , accountUUIDs , accountExternalIDs [] string , start , end time.Time , dimension string ) (map [string ]BreakdownValue , error ) {
34+ args := m .Called (ctx , accountUUIDs , accountExternalIDs , start , end , dimension )
3435 if args .Get (0 ) == nil {
3536 return nil , args .Error (1 )
3637 }
3738 return args .Get (0 ).(map [string ]BreakdownValue ), args .Error (1 )
3839}
3940
41+ // TestHandler_getHistoryAnalytics_ExternalIDOnlyAccount is the analytics
42+ // keystone (issue #701/#498/#866): selecting an account by its cloud_accounts
43+ // UUID resolves to (UUID, external_id) and both reach QueryHistory so rows that
44+ // carry only the external account_id (cloud_account_id NULL) are aggregated.
45+ func TestHandler_getHistoryAnalytics_ExternalIDOnlyAccount (t * testing.T ) {
46+ ctx := context .Background ()
47+ accountUUID := "bbbbbbbb-1111-2222-3333-444444444444"
48+ accountExternal := "999988887777"
49+
50+ mockClient := new (MockAnalyticsClient )
51+ mockClient .On ("QueryHistory" , ctx , []string {accountUUID }, []string {accountExternal }, mock .Anything , mock .Anything , "hourly" ).
52+ Return ([]HistoryDataPoint {{TotalSavings : 50.0 , PurchaseCount : 1 }}, & HistorySummary {TotalPurchases : 1 }, nil )
53+
54+ mockStore := new (MockConfigStore )
55+ mockStore .ListCloudAccountsFn = func (_ context.Context , _ config.CloudAccountFilter ) ([]config.CloudAccount , error ) {
56+ return []config.CloudAccount {{ID : accountUUID , Name : "Account B" , Provider : "aws" , ExternalID : accountExternal }}, nil
57+ }
58+
59+ mockAuth , req := adminAnalyticsReq (ctx )
60+ handler := & Handler {auth : mockAuth , analyticsClient : mockClient , config : mockStore }
61+
62+ result , err := handler .getHistoryAnalytics (ctx , req , map [string ]string {"account_id" : accountUUID })
63+ require .NoError (t , err )
64+ require .NotNil (t , result )
65+ mockClient .AssertCalled (t , "QueryHistory" , ctx , []string {accountUUID }, []string {accountExternal }, mock .Anything , mock .Anything , "hourly" )
66+ }
67+
4068// MockAnalyticsCollector is a mock implementation of AnalyticsCollectorInterface
4169type MockAnalyticsCollector struct {
4270 mock.Mock
@@ -72,10 +100,13 @@ func TestHandler_getHistoryAnalytics_Success(t *testing.T) {
72100 }
73101 summary := & HistorySummary {TotalPurchases : 8 , TotalMonthlySavings : 190.0 }
74102
75- mockClient .On ("QueryHistory" , ctx , "account-123" , mock .Anything , mock .Anything , "hourly" ).Return (dataPoints , summary , nil )
103+ // "account-123" is not a known UUID, so resolveSingleAccountFilterIDs
104+ // treats it as an external account number: QueryHistory is called with no
105+ // UUIDs and account-123 as the external id.
106+ mockClient .On ("QueryHistory" , ctx , []string (nil ), []string {"account-123" }, mock .Anything , mock .Anything , "hourly" ).Return (dataPoints , summary , nil )
76107
77108 mockAuth , req := adminAnalyticsReq (ctx )
78- handler := & Handler {auth : mockAuth , analyticsClient : mockClient }
109+ handler := & Handler {auth : mockAuth , analyticsClient : mockClient , config : new ( MockConfigStore ) }
79110
80111 params := map [string ]string {
81112 "account_id" : "account-123" ,
@@ -107,17 +138,18 @@ func TestHandler_getHistoryAnalytics_DefaultInterval(t *testing.T) {
107138 mockClient := new (MockAnalyticsClient )
108139
109140 dataPoints := []HistoryDataPoint {}
110- mockClient .On ("QueryHistory" , ctx , "" , mock .Anything , mock .Anything , "hourly" ).Return (dataPoints , (* HistorySummary )(nil ), nil )
141+ // Empty account_id: resolver returns no UUIDs and no external ids.
142+ mockClient .On ("QueryHistory" , ctx , []string (nil ), []string (nil ), mock .Anything , mock .Anything , "hourly" ).Return (dataPoints , (* HistorySummary )(nil ), nil )
111143
112144 mockAuth , req := adminAnalyticsReq (ctx )
113- handler := & Handler {auth : mockAuth , analyticsClient : mockClient }
145+ handler := & Handler {auth : mockAuth , analyticsClient : mockClient , config : new ( MockConfigStore ) }
114146
115147 params := map [string ]string {} // No interval specified
116148
117149 _ , err := handler .getHistoryAnalytics (ctx , req , params )
118150 require .NoError (t , err )
119151
120- mockClient .AssertCalled (t , "QueryHistory" , ctx , "" , mock .Anything , mock .Anything , "hourly" )
152+ mockClient .AssertCalled (t , "QueryHistory" , ctx , [] string ( nil ), [] string ( nil ) , mock .Anything , mock .Anything , "hourly" )
121153}
122154
123155func TestHandler_getHistoryAnalytics_InvalidDateRange (t * testing.T ) {
@@ -140,10 +172,10 @@ func TestHandler_getHistoryAnalytics_QueryError(t *testing.T) {
140172 ctx := context .Background ()
141173 mockClient := new (MockAnalyticsClient )
142174
143- mockClient .On ("QueryHistory" , ctx , mock .Anything , mock .Anything , mock .Anything , mock .Anything ).Return (nil , nil , errors .New ("query failed" ))
175+ mockClient .On ("QueryHistory" , ctx , mock .Anything , mock .Anything , mock .Anything , mock .Anything , mock . Anything ).Return (nil , nil , errors .New ("query failed" ))
144176
145177 mockAuth , req := adminAnalyticsReq (ctx )
146- handler := & Handler {auth : mockAuth , analyticsClient : mockClient }
178+ handler := & Handler {auth : mockAuth , analyticsClient : mockClient , config : new ( MockConfigStore ) }
147179
148180 params := map [string ]string {}
149181
@@ -185,10 +217,10 @@ func TestHandler_getHistoryBreakdown_Success(t *testing.T) {
185217 "lambda" : {PurchaseCount : 5 , TotalSavings : 100.0 },
186218 }
187219
188- mockClient .On ("QueryBreakdown" , ctx , "account-123" , mock .Anything , mock .Anything , "service" ).Return (breakdownData , nil )
220+ mockClient .On ("QueryBreakdown" , ctx , [] string ( nil ), [] string { "account-123" } , mock .Anything , mock .Anything , "service" ).Return (breakdownData , nil )
189221
190222 mockAuth , req := adminAnalyticsReq (ctx )
191- handler := & Handler {auth : mockAuth , analyticsClient : mockClient }
223+ handler := & Handler {auth : mockAuth , analyticsClient : mockClient , config : new ( MockConfigStore ) }
192224
193225 params := map [string ]string {
194226 "account_id" : "account-123" ,
@@ -220,17 +252,17 @@ func TestHandler_getHistoryBreakdown_DefaultDimension(t *testing.T) {
220252 mockClient := new (MockAnalyticsClient )
221253
222254 breakdownData := map [string ]BreakdownValue {}
223- mockClient .On ("QueryBreakdown" , ctx , "" , mock .Anything , mock .Anything , "service" ).Return (breakdownData , nil )
255+ mockClient .On ("QueryBreakdown" , ctx , [] string ( nil ), [] string ( nil ) , mock .Anything , mock .Anything , "service" ).Return (breakdownData , nil )
224256
225257 mockAuth , req := adminAnalyticsReq (ctx )
226- handler := & Handler {auth : mockAuth , analyticsClient : mockClient }
258+ handler := & Handler {auth : mockAuth , analyticsClient : mockClient , config : new ( MockConfigStore ) }
227259
228260 params := map [string ]string {} // No dimension specified
229261
230262 _ , err := handler .getHistoryBreakdown (ctx , req , params )
231263 require .NoError (t , err )
232264
233- mockClient .AssertCalled (t , "QueryBreakdown" , ctx , "" , mock .Anything , mock .Anything , "service" )
265+ mockClient .AssertCalled (t , "QueryBreakdown" , ctx , [] string ( nil ), [] string ( nil ) , mock .Anything , mock .Anything , "service" )
234266}
235267
236268func TestHandler_getHistoryBreakdown_InvalidDateRange (t * testing.T ) {
@@ -253,10 +285,10 @@ func TestHandler_getHistoryBreakdown_QueryError(t *testing.T) {
253285 ctx := context .Background ()
254286 mockClient := new (MockAnalyticsClient )
255287
256- mockClient .On ("QueryBreakdown" , ctx , mock .Anything , mock .Anything , mock .Anything , mock .Anything ).Return (nil , errors .New ("breakdown failed" ))
288+ mockClient .On ("QueryBreakdown" , ctx , mock .Anything , mock .Anything , mock .Anything , mock .Anything , mock . Anything ).Return (nil , errors .New ("breakdown failed" ))
257289
258290 mockAuth , req := adminAnalyticsReq (ctx )
259- handler := & Handler {auth : mockAuth , analyticsClient : mockClient }
291+ handler := & Handler {auth : mockAuth , analyticsClient : mockClient , config : new ( MockConfigStore ) }
260292
261293 params := map [string ]string {}
262294
0 commit comments