Skip to content

Commit

Permalink
Merge pull request cedar-policy#23 from strongdm/patjakdev/run-integr…
Browse files Browse the repository at this point in the history
…ation-tests-always

cedar-go: run integration tests as part of the regular test suite
  • Loading branch information
patjakdev authored Jul 15, 2024
2 parents 80549a3 + df85797 commit a71e93e
Show file tree
Hide file tree
Showing 7 changed files with 196 additions and 206 deletions.
23 changes: 8 additions & 15 deletions .github/workflows/corpus.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
name: Nightly Test Corpus
name: Check For Integration Test Corpus Updates
on:
workflow_dispatch:
schedule:
Expand All @@ -15,19 +15,12 @@ jobs:
steps:
- uses: actions/checkout@v4

- name: Set up Go
uses: actions/setup-go@v4
with:
go-version: "1.22"

- name: Download
run: curl -L -o integration_tests/corpus-tests.tar.gz https://raw.githubusercontent.com/cedar-policy/cedar-integration-tests/main/corpus-tests.tar.gz

- name: Extract
run: mkdir -p integration_tests/tmp && tar -xzf integration_tests/corpus-tests.tar.gz -C integration_tests/tmp
- name: Download Upstream Corpus
run: curl -L -o /tmp/corpus-tests.tar.gz https://raw.githubusercontent.com/cedar-policy/cedar-integration-tests/main/corpus-tests.tar.gz

- name: Corpus Tests
run: go test -count=1 -v -tags corpus ./integration_tests/... | tail
# cmp returns status code 1 if the files differ
- name: Compare
run: cmp /tmp/corpus-tests.tar.gz corpus-tests.tar.gz

- name: Notify on Failure
if: failure() && github.event_name == 'schedule'
Expand All @@ -38,8 +31,8 @@ jobs:
github.rest.issues.create({
owner: context.repo.owner,
repo: context.repo.repo,
title: 'Nightly Test Corpus Failed',
body: 'The Nightly Test Corpus workflow failed. Please investigate.',
title: 'Upstream Integration Test Corpus Modified',
body: 'The upstream integration test corpus at https://raw.githubusercontent.com/cedar-policy/cedar-integration-tests/main/corpus-tests.tar.gz has been updated. Please integrate the changes into the local copy.'
assignees: ['jmccarthy', 'philhassey', 'patjakdev'],
labels: ['bug']
})
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
.idea/
Binary file added corpus-tests.tar.gz
Binary file not shown.
187 changes: 187 additions & 0 deletions corpus_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,187 @@
package cedar

import (
"archive/tar"
"bytes"
"compress/gzip"
_ "embed"
"encoding/json"
"fmt"
"io"
"slices"
"strings"
"testing"
)

// jsonEntity is not part of entityValue as I can find
// no evidence this is part of the JSON spec. It also
// requires creating a parser, so it's quite expensive.
type jsonEntity EntityUID

func (e *jsonEntity) UnmarshalJSON(b []byte) error {
if string(b) == "null" {
return nil
}
var input EntityUID
if err := json.Unmarshal(b, &input); err != nil {
return err
}
*e = jsonEntity(input)
return nil
}

type corpusTest struct {
Schema string `json:"schema"`
Policies string `json:"policies"`
ShouldValidate bool `json:"shouldValidate"`
Entities string `json:"entities"`
Requests []struct {
Desc string `json:"description"`
Principal jsonEntity `json:"principal"`
Action jsonEntity `json:"action"`
Resource jsonEntity `json:"resource"`
Context Record `json:"context"`
Decision string `json:"decision"`
Reasons []string `json:"reason"`
Errors []string `json:"errors"`
} `json:"requests"`
}

//go:embed corpus-tests.tar.gz
var corpusArchive []byte

type tarFileDataPointer struct {
Position int64
Size int64
}

type TarFileMap struct {
buf io.ReaderAt
files map[string]tarFileDataPointer
}

func NewTarFileMap(buf io.ReaderAt) TarFileMap {
return TarFileMap{
buf: buf,
files: make(map[string]tarFileDataPointer),
}
}

func (fdm *TarFileMap) AddFileData(path string, position int64, size int64) {
fdm.files[path] = tarFileDataPointer{position, size}
}

func (fdm TarFileMap) GetFileData(path string) ([]byte, error) {
fdp, ok := fdm.files[path]
if !ok {
return nil, fmt.Errorf("file not found in archive: %v", path)
}
content := make([]byte, fdp.Size)
_, err := fdm.buf.ReadAt(content, fdp.Position)
if err != nil {
return nil, err
}

return content, nil
}

func TestCorpus(t *testing.T) {
t.Parallel()

gzipReader, err := gzip.NewReader(bytes.NewReader(corpusArchive))
if err != nil {
t.Fatal("error reading corpus compressed archive header", err)
}
defer gzipReader.Close()

buf, err := io.ReadAll(gzipReader)
if err != nil {
t.Fatal("error reading corpus compressed archive", err)
}

bufReader := bytes.NewReader(buf)
archiveReader := tar.NewReader(bufReader)

fdm := NewTarFileMap(bufReader)
var testFiles []string
for file, err := archiveReader.Next(); err == nil; file, err = archiveReader.Next() {
if file.Typeflag != tar.TypeReg {
continue
}

cursor, _ := bufReader.Seek(0, io.SeekCurrent)
fdm.AddFileData(file.Name, cursor, file.Size)

if strings.HasSuffix(file.Name, ".json") && !strings.HasSuffix(file.Name, ".entities.json") {
testFiles = append(testFiles, file.Name)
}
}

for _, testFile := range testFiles {
testFile := testFile
t.Run(testFile, func(t *testing.T) {
t.Parallel()
testContent, err := fdm.GetFileData(testFile)
if err != nil {
t.Fatal("error reading test content", err)
}

var tt corpusTest
if err := json.Unmarshal(testContent, &tt); err != nil {
t.Fatal("error unmarshalling test", err)
}

entitiesContent, err := fdm.GetFileData(tt.Entities)
if err != nil {
t.Fatal("error reading entities content", err)
}

var entities Entities
if err := json.Unmarshal(entitiesContent, &entities); err != nil {
t.Fatal("error unmarshalling test", err)
}

policyContent, err := fdm.GetFileData(tt.Policies)
if err != nil {
t.Fatal("error reading policy content", err)
}

policySet, err := NewPolicySet("policy.cedar", policyContent)
if err != nil {
t.Fatal("error parsing policy set", err)
}

for _, request := range tt.Requests {
t.Run(request.Desc, func(t *testing.T) {
t.Parallel()
ok, diag := policySet.IsAuthorized(
entities,
Request{
Principal: EntityUID(request.Principal),
Action: EntityUID(request.Action),
Resource: EntityUID(request.Resource),
Context: request.Context,
})

if ok != (request.Decision == "allow") {
t.Fatalf("got %v want %v", ok, request.Decision)
}
var errors []string
for _, n := range diag.Errors {
errors = append(errors, fmt.Sprintf("policy%d", n.Policy))
}
if !slices.Equal(errors, request.Errors) {
t.Errorf("errors got %v want %v", errors, request.Errors)
}
var reasons []string
for _, n := range diag.Reasons {
reasons = append(reasons, fmt.Sprintf("policy%d", n.Policy))
}
if !slices.Equal(reasons, request.Reasons) {
t.Errorf("reasons got %v want %v", reasons, request.Reasons)
}
})
}
})
}
}
1 change: 0 additions & 1 deletion integration_tests/.gitignore

This file was deleted.

47 changes: 0 additions & 47 deletions integration_tests/common.go

This file was deleted.

Loading

0 comments on commit a71e93e

Please sign in to comment.