Skip to content

Commit b871818

Browse files
authored
Fix: "all_teams" data resource causes 429 status code requests (#988)
* Fix: "all_teams" data resource causes 429 status code requests * added a note * linting fixes * Added memoize tests
1 parent 8a80a6a commit b871818

File tree

6 files changed

+112
-7
lines changed

6 files changed

+112
-7
lines changed

client/api_client.go

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ type ApiClient struct {
1010
http http.HttpClientInterface
1111
cachedOrganizationId string
1212
defaultOrganizationId string
13+
memoizedGetTeams func(string) ([]Team, error)
1314
}
1415

1516
type ApiClientInterface interface {
@@ -172,9 +173,13 @@ type ApiClientInterface interface {
172173
}
173174

174175
func NewApiClient(client http.HttpClientInterface, defaultOrganizationId string) ApiClientInterface {
175-
return &ApiClient{
176+
apiClient := &ApiClient{
176177
http: client,
177178
cachedOrganizationId: "",
178179
defaultOrganizationId: defaultOrganizationId,
179180
}
181+
182+
apiClient.memoizedGetTeams = memoize(apiClient.GetTeams)
183+
184+
return apiClient
180185
}

client/configuration_variable_test.go

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ package client_test
22

33
import (
44
"encoding/json"
5-
"strings"
65
"testing"
76

87
. "github.com/env0/terraform-provider-env0/client"
@@ -309,7 +308,7 @@ func TestConfigurationVariableMarshelling(t *testing.T) {
309308

310309
b, err := json.Marshal(&variable)
311310
if assert.NoError(t, err) {
312-
assert.False(t, strings.Contains(string(b), str))
311+
assert.NotContains(t, string(b), str)
313312
}
314313

315314
type ConfigurationVariableDummy ConfigurationVariable
@@ -318,7 +317,7 @@ func TestConfigurationVariableMarshelling(t *testing.T) {
318317

319318
b, err = json.Marshal(&dummy)
320319
if assert.NoError(t, err) {
321-
assert.True(t, strings.Contains(string(b), str))
320+
assert.Contains(t, string(b), str)
322321
}
323322

324323
var variable2 ConfigurationVariable

client/memoize.go

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
package client
2+
3+
type memoizedResult[V any] struct {
4+
value V
5+
err error
6+
}
7+
8+
func memoize[K comparable, V any](f func(K) (V, error)) func(K) (V, error) {
9+
cache := make(map[K]memoizedResult[V])
10+
11+
return func(key K) (V, error) {
12+
if res, ok := cache[key]; ok {
13+
return res.value, res.err
14+
}
15+
16+
value, err := f(key)
17+
18+
cache[key] = memoizedResult[V]{value: value, err: err}
19+
20+
return value, err
21+
}
22+
}
23+
24+
// MemoizeExported exports the memoize function for testing
25+
func MemoizeExported[K comparable, V any](f func(K) (V, error)) func(K) (V, error) {
26+
return memoize(f)
27+
}

client/memoize_test.go

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
package client_test
2+
3+
import (
4+
"errors"
5+
"testing"
6+
7+
"github.com/env0/terraform-provider-env0/client"
8+
"github.com/stretchr/testify/assert"
9+
"github.com/stretchr/testify/require"
10+
)
11+
12+
func TestMemoize(t *testing.T) {
13+
t.Run("should cache successful results", func(t *testing.T) {
14+
callCount := 0
15+
f := func(s string) ([]client.Team, error) {
16+
callCount++
17+
18+
return []client.Team{{Name: s}}, nil
19+
}
20+
21+
memoized := client.MemoizeExported(f)
22+
23+
// First call
24+
result1, err1 := memoized("")
25+
require.NoError(t, err1)
26+
assert.Len(t, result1, 1)
27+
assert.Equal(t, 1, callCount)
28+
29+
// Second call with same input - should use cache
30+
result2, err2 := memoized("")
31+
require.NoError(t, err2)
32+
assert.Len(t, result2, 1)
33+
assert.Equal(t, 1, callCount) // Count shouldn't increase
34+
35+
// Different input - should call function again
36+
result3, err3 := memoized("test")
37+
require.NoError(t, err3)
38+
assert.Len(t, result3, 1)
39+
assert.Equal(t, "test", result3[0].Name)
40+
assert.Equal(t, 2, callCount)
41+
})
42+
43+
t.Run("should cache errors", func(t *testing.T) {
44+
callCount := 0
45+
expectedError := errors.New("test error")
46+
f := func(s string) ([]client.Team, error) {
47+
callCount++
48+
49+
return nil, expectedError
50+
}
51+
52+
memoized := client.MemoizeExported(f)
53+
54+
// First call
55+
result1, err1 := memoized("")
56+
require.Error(t, err1)
57+
assert.Equal(t, expectedError, err1)
58+
assert.Nil(t, result1)
59+
assert.Equal(t, 1, callCount)
60+
61+
// Second call - should return cached error
62+
result2, err2 := memoized("")
63+
require.Error(t, err2)
64+
assert.Equal(t, expectedError, err2)
65+
assert.Nil(t, result2)
66+
assert.Equal(t, 1, callCount) // Count shouldn't increase
67+
})
68+
}

client/team.go

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,12 @@ func (client *ApiClient) TeamUpdate(id string, payload TeamUpdatePayload) (Team,
8383
return result, nil
8484
}
8585

86-
func (client *ApiClient) GetTeams(params map[string]string) ([]Team, error) {
86+
func (client *ApiClient) GetTeams(name string) ([]Team, error) {
87+
params := map[string]string{"limit": "100"}
88+
if name != "" {
89+
params["name"] = name
90+
}
91+
8792
organizationId, err := client.OrganizationId()
8893
if err != nil {
8994
return nil, err
@@ -112,9 +117,9 @@ func (client *ApiClient) GetTeams(params map[string]string) ([]Team, error) {
112117
}
113118

114119
func (client *ApiClient) Teams() ([]Team, error) {
115-
return client.GetTeams(map[string]string{"limit": "100"})
120+
return client.memoizedGetTeams("")
116121
}
117122

118123
func (client *ApiClient) TeamsByName(name string) ([]Team, error) {
119-
return client.GetTeams(map[string]string{"name": name, "limit": "100"})
124+
return client.memoizedGetTeams(name)
120125
}

env0/data_teams.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import (
1111
func dataTeams() *schema.Resource {
1212
return &schema.Resource{
1313
ReadContext: dataTeamsRead,
14+
Description: "Note: this data source is cached, once fetched it will not be updated until the next plan/apply",
1415

1516
Schema: map[string]*schema.Schema{
1617
"names": {

0 commit comments

Comments
 (0)