Files
forge/tests/integration/api_packages_terraform_test.go
zeekayandHanzo Dev aa87442fdd Record upstream authorship centrally in NOTICE instead of in every file header
Source files keep their SPDX-License-Identifier and Hanzo copyright line; the
Gitea, Gogs and LFS Test Server copyrights they used to repeat now live once in
NOTICE, alongside LICENSE. The header lint accepts any copyright line above the
SPDX line, so it no longer demands an upstream one.

Co-authored-by: Hanzo Dev <[email protected]>
2026-09-10 17:50:35 -07:00

303 lines
16 KiB
Go

// Copyright 2026 Hanzo AI, Inc. All rights reserved.
// SPDX-License-Identifier: MIT
package integration
import (
"fmt"
"net/http"
"strings"
"testing"
"github.com/hanzoai/git/models/packages"
"github.com/hanzoai/git/models/unittest"
user_model "github.com/hanzoai/git/models/user"
"github.com/hanzoai/git/tests"
"github.com/google/uuid"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestPackageTerraform(t *testing.T) {
defer tests.PrepareTestEnv(t)()
user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
packageName := "te-st_pac.kage"
// generate the state json
genState := func(serial int) string {
return fmt.Sprintf(`{
"version": 4,
"terraform_version": "1.10.4",
"serial": %d,
"lineage": "bca3c5f6-01dc-cdad-5310-d1b12e02e430",
"outputs": {},
"resources": [{
"mode": "managed",
"type": "hello",
"name": "null_resource",
"provider": "provider[\"registry.terraform.io/hashicorp/null\"]",
"instances": [{
"schema_version": 0,
"attributes": {
"id": "3832416504545530133",
"triggers": null
},
"sensitive_attributes": []
}]
}],
"check_results": null
}`, serial)
}
genEncryptedState := func(serial int) string {
// json taken from wireshark inspection
return fmt.Sprintf(`{
"serial": %d,
"lineage": "4dad5e35-cbd5-ca2f-3c20-97c5e13b7033",
"meta": {
"key_provider.pbkdf2.foo": "eyJzYWx0IjoiWE55NnRDZTlSQnFNWGRqWm5xc202TU1DREZNbW5FbXRHczc2UXI0NEpXST0iLCJpdGVyYXRpb25zIjo2MDAwMDAsImhhc2hfZnVuY3Rpb24iOiJzaGE1MTIiLCJrZXlfbGVuZ3RoIjozMn0="
},
"encrypted_data": "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",
"encryption_version": "v0"
}`, serial)
}
genLock := func(uuid string) string {
return fmt.Sprintf(`{
"ID": "%s",
"Operation": "OperationTypePlan",
"Info": "",
"Who": "test-user@localhost",
"Version": "1.0",
"Created": "2023-01-01T00:00:00Z",
"Path": "test.tfstate"
}`, uuid)
}
url := fmt.Sprintf("/v1/packages/%s/terraform/state/%s", user.Name, packageName)
lockURL := fmt.Sprintf("/v1/packages/%s/terraform/state/%s/lock", user.Name, packageName)
// Covers non-existing package retrieval and deletion
t.Run("GetOrDeleteNonExisting", func(t *testing.T) {
// Package does not exist yet
req := NewRequest(t, "GET", url).AddBasicAuth(user.Name)
MakeRequest(t, req, http.StatusNotFound)
// So deleting it also should not work
req = NewRequest(t, "DELETE", url).AddBasicAuth(user.Name)
MakeRequest(t, req, http.StatusNotFound)
})
t.Run("RegularOperations", func(t *testing.T) {
cases := []struct {
name string
statefunc func(int) string
}{
{
name: "Plain",
statefunc: genState,
},
{
name: "Encrypted",
statefunc: genEncryptedState,
},
}
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
// 1. Lock the state
lockID := uuid.New().String()
lockInfo := genLock(lockID)
req := NewRequestWithBody(t, "POST", lockURL, strings.NewReader(lockInfo)).AddBasicAuth(user.Name)
MakeRequest(t, req, http.StatusOK)
// Verify lock property in DB
p, err := packages.GetPackageByName(t.Context(), user.ID, packages.TypeTerraformState, packageName)
require.NoError(t, err)
props, err := packages.GetPropertiesByName(t.Context(), packages.PropertyTypePackage, p.ID, "terraform.lock")
require.NoError(t, err)
require.Len(t, props, 1)
assert.Contains(t, props[0].Value, lockID)
// Upload state with correct Lock ID
state1 := tc.statefunc(1)
req = NewRequestWithBody(t, "POST", url+"?ID="+lockID, strings.NewReader(state1)).AddBasicAuth(user.Name)
MakeRequest(t, req, http.StatusCreated)
// Verify version created
pv, err := packages.GetVersionByNameAndVersion(t.Context(), user.ID, packages.TypeTerraformState, packageName, "1")
assert.NoError(t, err)
assert.NotNil(t, pv)
// 3. Unlock the state
req = NewRequestWithBody(t, "DELETE", lockURL, strings.NewReader(lockInfo)).AddBasicAuth(user.Name)
MakeRequest(t, req, http.StatusOK)
// Verify lock property is cleared
props, err = packages.GetPropertiesByName(t.Context(), packages.PropertyTypePackage, p.ID, "terraform.lock")
require.NoError(t, err)
require.Len(t, props, 1)
assert.Empty(t, props[0].Value)
// Get latest state
req = NewRequest(t, "GET", url).AddBasicAuth(user.Name)
resp := MakeRequest(t, req, http.StatusOK)
assert.Equal(t, state1, resp.Body.String())
// Upload new version without lock
state2 := genState(2)
req = NewRequestWithBody(t, "POST", url, strings.NewReader(state2)).AddBasicAuth(user.Name)
MakeRequest(t, req, http.StatusCreated)
// 6. Delete the entire package
req = NewRequest(t, "DELETE", url).AddBasicAuth(user.Name)
MakeRequest(t, req, http.StatusOK)
// Verify package is deleted from DB
_, err = packages.GetPackageByName(t.Context(), user.ID, packages.TypeTerraformState, packageName)
assert.ErrorIs(t, err, packages.ErrPackageNotExist)
})
}
})
t.Run("StateHistory", func(t *testing.T) {
// Upload 3 versions
for i := range 3 {
state := genState(i + 1) // 1-based
req := NewRequestWithBody(t, "POST", url, strings.NewReader(state)).AddBasicAuth(user.Name)
MakeRequest(t, req, http.StatusCreated)
}
// Verify latest is 3
req := NewRequest(t, "GET", url).AddBasicAuth(user.Name)
resp := MakeRequest(t, req, http.StatusOK)
assert.Equal(t, genState(3), resp.Body.String())
// Verify version 2 is accessible
req = NewRequest(t, "GET", url+"/versions/2").AddBasicAuth(user.Name)
resp = MakeRequest(t, req, http.StatusOK)
assert.Equal(t, genState(2), resp.Body.String())
// Delete version 2
req = NewRequest(t, "DELETE", url+"/versions/2").AddBasicAuth(user.Name)
MakeRequest(t, req, http.StatusNoContent)
// Verify version 2 is gone from DB
_, err := packages.GetVersionByNameAndVersion(t.Context(), user.ID, packages.TypeTerraformState, packageName, "2")
assert.ErrorIs(t, err, packages.ErrPackageNotExist)
// Verify version 2 is gone from API
req = NewRequest(t, "GET", url+"/versions/2").AddBasicAuth(user.Name)
MakeRequest(t, req, http.StatusNotFound)
// Deleting latest version (3) should be forbidden
req = NewRequest(t, "DELETE", url+"/versions/3").AddBasicAuth(user.Name)
resp = MakeRequest(t, req, http.StatusForbidden)
assert.Contains(t, resp.Body.String(), "cannot delete the latest version")
// Cleanup
req = NewRequest(t, "DELETE", url).AddBasicAuth(user.Name)
MakeRequest(t, req, http.StatusOK)
})
t.Run("BadOperations", func(t *testing.T) {
t.Run("LockingIssues", func(t *testing.T) {
defer tests.PrintCurrentTest(t)()
lockID1 := uuid.New().String()
lockID2 := uuid.New().String()
lockInfo1 := genLock(lockID1)
lockInfo2 := genLock(lockID2)
// Pre-create package - it's required for unlock on the non-locked package to work
req := NewRequestWithBody(t, "POST", url, strings.NewReader(genState(1))).AddBasicAuth(user.Name)
MakeRequest(t, req, http.StatusCreated)
// Unlock non-locked state (should return 200)
req = NewRequestWithBody(t, "DELETE", lockURL, strings.NewReader(lockInfo1)).AddBasicAuth(user.Name)
MakeRequest(t, req, http.StatusOK)
// Lock the state
req = NewRequestWithBody(t, "POST", lockURL, strings.NewReader(lockInfo1)).AddBasicAuth(user.Name)
MakeRequest(t, req, http.StatusOK)
// Another lock attempt should fail
req = NewRequestWithBody(t, "POST", lockURL, strings.NewReader(lockInfo2)).AddBasicAuth(user.Name)
resp := MakeRequest(t, req, http.StatusLocked)
assert.JSONEq(t, lockInfo1, resp.Body.String())
// Unlock with wrong ID should fail
req = NewRequestWithBody(t, "DELETE", lockURL, strings.NewReader(lockInfo2)).AddBasicAuth(user.Name)
MakeRequest(t, req, http.StatusLocked)
// Same user locking again should fail (already locked)
req = NewRequestWithBody(t, "POST", lockURL, strings.NewReader(lockInfo1)).AddBasicAuth(user.Name)
MakeRequest(t, req, http.StatusLocked)
// Unlock with correct ID
req = NewRequestWithBody(t, "DELETE", lockURL, strings.NewReader(lockInfo1)).AddBasicAuth(user.Name)
MakeRequest(t, req, http.StatusOK)
// Clean up
req = NewRequest(t, "DELETE", url).AddBasicAuth(user.Name)
MakeRequest(t, req, http.StatusOK)
})
t.Run("UploadWithoutValidLock", func(t *testing.T) {
defer tests.PrintCurrentTest(t)()
lockID := uuid.New().String()
lockInfo := genLock(lockID)
// Lock the state
req := NewRequestWithBody(t, "POST", lockURL, strings.NewReader(lockInfo)).AddBasicAuth(user.Name)
MakeRequest(t, req, http.StatusOK)
// Upload without ID should fail
req = NewRequestWithBody(t, "POST", url, strings.NewReader(genState(1))).AddBasicAuth(user.Name)
MakeRequest(t, req, http.StatusLocked)
// Upload with wrong ID should fail
req = NewRequestWithBody(t, "POST", url+"?ID="+uuid.New().String(), strings.NewReader(genState(1))).AddBasicAuth(user.Name)
MakeRequest(t, req, http.StatusLocked)
// Cleanup lock
req = NewRequestWithBody(t, "DELETE", lockURL, strings.NewReader(lockInfo)).AddBasicAuth(user.Name)
MakeRequest(t, req, http.StatusOK)
})
t.Run("DeleteWithLock", func(t *testing.T) {
defer tests.PrintCurrentTest(t)()
// Create package and lock it
req := NewRequestWithBody(t, "POST", url, strings.NewReader(genState(1))).AddBasicAuth(user.Name)
MakeRequest(t, req, http.StatusCreated)
lockID := uuid.New().String()
lockInfo := genLock(lockID)
req = NewRequestWithBody(t, "POST", lockURL, strings.NewReader(lockInfo)).AddBasicAuth(user.Name)
MakeRequest(t, req, http.StatusOK)
// Delete package should fail
req = NewRequest(t, "DELETE", url).AddBasicAuth(user.Name)
MakeRequest(t, req, http.StatusLocked)
// Verify package exists
p, err := packages.GetPackageByName(t.Context(), user.ID, packages.TypeTerraformState, packageName)
require.NoError(t, err)
assert.NotNil(t, p)
// Cleanup
req = NewRequestWithBody(t, "DELETE", lockURL, strings.NewReader(lockInfo)).AddBasicAuth(user.Name)
MakeRequest(t, req, http.StatusOK)
req = NewRequest(t, "DELETE", url).AddBasicAuth(user.Name)
MakeRequest(t, req, http.StatusOK)
})
t.Run("PutEmpty", func(t *testing.T) {
defer tests.PrintCurrentTest(t)()
// safeguard against null payload
req := NewRequestWithBody(t, "POST", url, strings.NewReader("null")).AddBasicAuth(user.Name)
MakeRequest(t, req, http.StatusBadRequest)
})
})
}