Skip to content

Commit

Permalink
testing: add test cases to verify cache invalidation for LocalPolicyR…
Browse files Browse the repository at this point in the history
…etriever

At time of creation these tests fail with:
```
=== RUN   TestCacheInvalidationLocalPolicyRetrieverIfPolicyIsChanged
    ./cmd/policy-retrieval_test.go:271: Policy arn:aws:iam::000000000000:role/cache-invalidation2 was updated at 2024-12-14 15:53:10.511022552 +0100 CET m=+0.003016620 and now 2024-12-14 15:53:15.520616568 +0100 CET m=+5.012610662 policy manager still sees {
                "Version": "2012-10-17",
                "Statement": [
                        {
                                "Effect": "Allow",
                                "Action": "s3:*",
                                "Resource": "*",
                                "Condition" : {
                                                "StringLike" : {
                                                                "aws:RequestedRegion": "tst-1"
                                                }
                                }
                        }
                ]
        }
--- FAIL: TestCacheInvalidationLocalPolicyRetrieverIfPolicyIsChanged (5.01s)
```
and
```
=== RUN   TestCacheInvalidationLocalPolicyRetrieverIfPolicyIsChanged
    ./cmd/policy-retrieval_test.go:271: Policy arn:aws:iam::000000000000:role/cache-invalidation2 was updated at 2024-12-14 15:53:10.511022552 +0100 CET m=+0.003016620 and now 2024-12-14 15:53:15.520616568 +0100 CET m=+5.012610662 policy manager still sees {
                "Version": "2012-10-17",
                "Statement": [
                        {
                                "Effect": "Allow",
                                "Action": "s3:*",
                                "Resource": "*",
                                "Condition" : {
                                                "StringLike" : {
                                                                "aws:RequestedRegion": "tst-1"
                                                }
                                }
                        }
                ]
        }
--- FAIL: TestCacheInvalidationLocalPolicyRetrieverIfPolicyIsChanged (5.01s)
```

These tests are expected to pass with proper cache invalidation and they also would take less long. If cache invalidation would take longer than 5 seconds that variable can be further tuned but a higher value would also impact user experience.
  • Loading branch information
Peter Van Bouwel committed Dec 14, 2024
1 parent 6267299 commit 618c3ff
Show file tree
Hide file tree
Showing 2 changed files with 146 additions and 1 deletion.
101 changes: 100 additions & 1 deletion cmd/policy-retrieval_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package cmd

import (
"fmt"
"os"
"strings"
"testing"
"time"
Expand Down Expand Up @@ -172,4 +173,102 @@ func TestPolicyManagerPrewarm(t *testing.T) {
if !pm.DoesPolicyExist(expectedPolicy) {
t.Errorf("Missing policy %s", expectedPolicy)
}
}
}


func createTestPolicyFileForLocalPolicyRetriever(policyArn, policyContent string, pr *LocalPolicyRetriever, t *testing.T) {
policyFileName := pr.getPolicyPath(policyArn)
f, err := os.Create(policyFileName)
checkErrorTestDependency(err, t, fmt.Sprintf("Could Not create policy file %s", policyFileName))

f.Write([]byte(policyContent))

defer f.Close()
}

func deleteTestPolicyFileForLocalPolicyRetriever(policyArn string, pr *LocalPolicyRetriever, t *testing.T) {
policyFileName := pr.getPolicyPath(policyArn)
err := os.Remove(policyFileName)
checkErrorTestDependency(err, t, fmt.Sprintf("Could not delete policy file %s", policyFileName))
}


func TestCacheInvalidationLocalPolicyRetrieverIfPolicyIsRemoved(t *testing.T) {
//Given a policy manager that is backed by a local PolicyRetriever
pr := NewLocalPolicyRetriever(t.TempDir())
pm := NewPolicyManager(pr)
//Given a test Arn
testArn := "arn:aws:iam::000000000000:role/cache-invalidation"

//WHEN the policy file exists in the expected place
createTestPolicyFileForLocalPolicyRetriever(testArn, testPolicyAllowAll, pr, t)
//THEN it must exist as per the Policy Manager
if !pm.DoesPolicyExist(testArn) {
t.Errorf("Policy %s should have existed but it did not", testArn)
t.FailNow()
}

//WHEN the policyFile gets deleted
deleteTestPolicyFileForLocalPolicyRetriever(testArn, pr, t)
deletionTime := time.Now()

var policyManagerKnowsPolicyDoesNotExist predicateFunction = func () bool{
return !pm.DoesPolicyExist(testArn)
}

//THEN in due time it should no longer exist
if !isTrueWithinDueTime(policyManagerKnowsPolicyDoesNotExist) {
t.Errorf("Policy %s was removed at %s and now %s policy manager still thinks it exists", testArn, deletionTime, time.Now())
t.FailNow()
}
}


func TestCacheInvalidationLocalPolicyRetrieverIfPolicyIsChanged(t *testing.T) {
//Given a policy manager that is backed by a local PolicyRetriever
pr := NewLocalPolicyRetriever(t.TempDir())
pm := NewPolicyManager(pr)
//Given 2 test Arn
testArn1 := "arn:aws:iam::000000000000:role/cache-invalidation1"
testArn2 := "arn:aws:iam::000000000000:role/cache-invalidation2"

//WHEN the policy files exists in the expected place and are policies without time conditions
createTestPolicyFileForLocalPolicyRetriever(testArn1, testPolicyAllowAll, pr, t)
createTestPolicyFileForLocalPolicyRetriever(testArn2, testPolicyAllowAllInRegion1, pr, t)

//THEN the templates actually differ
pol1, err1 := pm.GetPolicy(testArn1, &PolicySessionData{})
checkErrorTestDependency(err1, t, "Policy1 should have been retrievable")
pol2, err2 := pm.GetPolicy(testArn2, &PolicySessionData{})
checkErrorTestDependency(err2, t, "Policy2 should have been retrievable")

if pol1 == pol2 {
t.Errorf("Policies should have been different but both gave: %s", pol1)
t.FailNow()
}

//WHEN the 2nd policy gets updated such that it has the same content as the first.
deleteTestPolicyFileForLocalPolicyRetriever(testArn2, pr, t)
createTestPolicyFileForLocalPolicyRetriever(testArn2, testPolicyAllowAll, pr, t)

updateTime := time.Now()

var policyManagerSeesUpdate predicateFunction = func () bool{
pol1, err1 := pm.GetPolicy(testArn1, &PolicySessionData{})
checkErrorTestDependency(err1, t, "Policy1 should have been retrievable")
pol2, err2 := pm.GetPolicy(testArn2, &PolicySessionData{})
checkErrorTestDependency(err2, t, "Policy2 should have been retrievable")

return pol1 == pol2
}

//THEN in due time it should no longer exist
if !isTrueWithinDueTime(policyManagerSeesUpdate) {
polText, err := pm.GetPolicy(testArn2, &PolicySessionData{})
if err != nil {
polText = err.Error()
}
t.Errorf("Policy %s was updated at %s and now %s policy manager still sees %s", testArn2, updateTime, time.Now(), polText)
t.FailNow()
}
}
46 changes: 46 additions & 0 deletions cmd/test-utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@ package cmd
import (
"encoding/json"
"os"
"strings"
"testing"
"time"
)


Expand All @@ -26,4 +28,48 @@ func skipIfNoTestingBackends(t *testing.T) {
if os.Getenv("NO_TESTING_BACKENDS") != "" {
t.Skip("Skipping this test because no testing backends and that is a dependency for thist test.")
}
}

//checkErrorTestDependency check for errors to pracitce safe programming but where you do not really
//expect problems (but cannot guarantee them not happening e.g. because of the execution environment).
//This is only to be used in test cases and will fail the test you can use msg to pass extra context info
func checkErrorTestDependency(err error, t *testing.T, msg ...string) {
var strMsg string
if len(msg) > 0 {
strMsg = strings.Join(msg, ", ")
}
if err != nil {
t.Errorf("Encountered error %s which should not occure. %s", err, strMsg)
t.FailNow()
}
}

type predicateFunction func() bool

//isTrueWithinDueTime takes a function that takes no arguments but returns a boolean
//and will await for maximum the first waitTime (which defaults to 5 seconds) and will
//check every second waitTime (defaults to 10 milliseconds)
func isTrueWithinDueTime(callable predicateFunction, waitTimes ...time.Duration) bool {
var maxWaitTime time.Duration = 5 * time.Second
var waitTimeBetweenChecks time.Duration = 10 * time.Millisecond

if len(waitTimes) > 0 {
maxWaitTime = waitTimes[0]
}
giveUpTime := time.Now().Add(maxWaitTime)

if len(waitTimes) > 1 {
waitTimeBetweenChecks = waitTimes[1]
}

for { //infinite loop
if callable() {
return true
}
if time.Now().After(giveUpTime) {
return false // time to give up
}
time.Sleep(waitTimeBetweenChecks)
}

}

0 comments on commit 618c3ff

Please sign in to comment.