mirror of
https://github.com/zitadel/zitadel.git
synced 2025-08-11 20:17:32 +00:00
tie loose ends, documentation
This commit is contained in:
4
.github/workflows/integration.yml
vendored
4
.github/workflows/integration.yml
vendored
@@ -9,7 +9,7 @@ on:
|
|||||||
- '**'
|
- '**'
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
integration-tests:
|
run:
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
db: [cockroach, postgres]
|
db: [cockroach, postgres]
|
||||||
@@ -36,7 +36,7 @@ jobs:
|
|||||||
- name: Download Go modules
|
- name: Download Go modules
|
||||||
run: go mod download
|
run: go mod download
|
||||||
- name: Start ${{ matrix.db }} database
|
- name: Start ${{ matrix.db }} database
|
||||||
run: docker compose -f e2e/config/integration/docker-compose.yaml up --wait ${INTEGRATION_DB_FLAVOR}
|
run: docker compose -f internal/integration/config/docker-compose.yaml up --wait ${INTEGRATION_DB_FLAVOR}
|
||||||
- name: Run zitadel init and setup
|
- name: Run zitadel init and setup
|
||||||
run: |
|
run: |
|
||||||
go run main.go init --config internal/integration/config/zitadel.yaml --config internal/integration/config/${INTEGRATION_DB_FLAVOR}.yaml
|
go run main.go init --config internal/integration/config/zitadel.yaml --config internal/integration/config/${INTEGRATION_DB_FLAVOR}.yaml
|
||||||
|
@@ -199,6 +199,21 @@ When you are happy with your changes, you can cleanup your environment.
|
|||||||
docker compose --file ./e2e/config/host.docker.internal/docker-compose.yaml down
|
docker compose --file ./e2e/config/host.docker.internal/docker-compose.yaml down
|
||||||
```
|
```
|
||||||
|
|
||||||
|
#### Integration tests
|
||||||
|
|
||||||
|
In order to run the integrations tests for the gRPC API, PostgreSQL and CockroachDB must be running and initialized.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
export INTEGRATION_DB_FLAVOR="cockroach"
|
||||||
|
docker compose -f internal/integration/config/docker-compose.yaml up --wait ${INTEGRATION_DB_FLAVOR}
|
||||||
|
go run main.go init --config internal/integration/config/zitadel.yaml --config internal/integration/config/${INTEGRATION_DB_FLAVOR}.yaml
|
||||||
|
go run main.go setup --masterkey MasterkeyNeedsToHave32Characters --config internal/integration/config/zitadel.yaml --config internal/integration/config/${INTEGRATION_DB_FLAVOR}.yaml
|
||||||
|
go test -tags=integration -race -parallel 1 ./internal/integration ./internal/api/grpc/...
|
||||||
|
docker compose -f internal/integration/config/docker-compose.yaml down
|
||||||
|
```
|
||||||
|
|
||||||
|
The above can be repeated with `INTEGRATION_DB_FLAVOR="postgres"`.
|
||||||
|
|
||||||
### Console
|
### Console
|
||||||
|
|
||||||
By executing the commands from this section, you run everything you need to develop the console locally.
|
By executing the commands from this section, you run everything you need to develop the console locally.
|
||||||
|
@@ -21,6 +21,7 @@ import (
|
|||||||
"github.com/zitadel/saml/pkg/provider"
|
"github.com/zitadel/saml/pkg/provider"
|
||||||
"golang.org/x/net/http2"
|
"golang.org/x/net/http2"
|
||||||
"golang.org/x/net/http2/h2c"
|
"golang.org/x/net/http2/h2c"
|
||||||
|
"golang.org/x/sys/unix"
|
||||||
|
|
||||||
"github.com/zitadel/zitadel/cmd/key"
|
"github.com/zitadel/zitadel/cmd/key"
|
||||||
cmd_tls "github.com/zitadel/zitadel/cmd/tls"
|
cmd_tls "github.com/zitadel/zitadel/cmd/tls"
|
||||||
@@ -341,10 +342,21 @@ func startAPIs(
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func reusePort(network, address string, conn syscall.RawConn) error {
|
||||||
|
return conn.Control(func(descriptor uintptr) {
|
||||||
|
err := syscall.SetsockoptInt(int(descriptor), syscall.SOL_SOCKET, unix.SO_REUSEPORT, 1)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
func listen(ctx context.Context, router *mux.Router, port uint16, tlsConfig *tls.Config, shutdown <-chan os.Signal) error {
|
func listen(ctx context.Context, router *mux.Router, port uint16, tlsConfig *tls.Config, shutdown <-chan os.Signal) error {
|
||||||
http2Server := &http2.Server{}
|
http2Server := &http2.Server{}
|
||||||
http1Server := &http.Server{Handler: h2c.NewHandler(router, http2Server), TLSConfig: tlsConfig}
|
http1Server := &http.Server{Handler: h2c.NewHandler(router, http2Server), TLSConfig: tlsConfig}
|
||||||
lis, err := net.Listen("tcp", fmt.Sprintf(":%d", port))
|
|
||||||
|
lc := &net.ListenConfig{Control: reusePort}
|
||||||
|
lis, err := lc.Listen(ctx, "tcp", fmt.Sprintf(":%d", port))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("tcp listener on %d failed: %w", port, err)
|
return fmt.Errorf("tcp listener on %d failed: %w", port, err)
|
||||||
}
|
}
|
||||||
|
@@ -57,7 +57,7 @@ func TestServer_AddHumanUser(t *testing.T) {
|
|||||||
&user.AddHumanUserRequest{
|
&user.AddHumanUserRequest{
|
||||||
Organisation: &object.Organisation{
|
Organisation: &object.Organisation{
|
||||||
Org: &object.Organisation_OrgId{
|
Org: &object.Organisation_OrgId{
|
||||||
OrgId: "211137963315232910",
|
OrgId: Tester.Organisation.ID,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Profile: &user.SetHumanProfile{
|
Profile: &user.SetHumanProfile{
|
||||||
@@ -97,7 +97,7 @@ func TestServer_AddHumanUser(t *testing.T) {
|
|||||||
&user.AddHumanUserRequest{
|
&user.AddHumanUserRequest{
|
||||||
Organisation: &object.Organisation{
|
Organisation: &object.Organisation{
|
||||||
Org: &object.Organisation_OrgId{
|
Org: &object.Organisation_OrgId{
|
||||||
OrgId: "211137963315232910",
|
OrgId: Tester.Organisation.ID,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Profile: &user.SetHumanProfile{
|
Profile: &user.SetHumanProfile{
|
||||||
@@ -142,7 +142,7 @@ func TestServer_AddHumanUser(t *testing.T) {
|
|||||||
&user.AddHumanUserRequest{
|
&user.AddHumanUserRequest{
|
||||||
Organisation: &object.Organisation{
|
Organisation: &object.Organisation{
|
||||||
Org: &object.Organisation_OrgId{
|
Org: &object.Organisation_OrgId{
|
||||||
OrgId: "211137963315232910",
|
OrgId: Tester.Organisation.ID,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Profile: &user.SetHumanProfile{
|
Profile: &user.SetHumanProfile{
|
||||||
@@ -188,7 +188,7 @@ func TestServer_AddHumanUser(t *testing.T) {
|
|||||||
&user.AddHumanUserRequest{
|
&user.AddHumanUserRequest{
|
||||||
Organisation: &object.Organisation{
|
Organisation: &object.Organisation{
|
||||||
Org: &object.Organisation_OrgId{
|
Org: &object.Organisation_OrgId{
|
||||||
OrgId: "211137963315232910",
|
OrgId: Tester.Organisation.ID,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Profile: &user.SetHumanProfile{
|
Profile: &user.SetHumanProfile{
|
||||||
@@ -229,7 +229,7 @@ func TestServer_AddHumanUser(t *testing.T) {
|
|||||||
&user.AddHumanUserRequest{
|
&user.AddHumanUserRequest{
|
||||||
Organisation: &object.Organisation{
|
Organisation: &object.Organisation{
|
||||||
Org: &object.Organisation_OrgId{
|
Org: &object.Organisation_OrgId{
|
||||||
OrgId: "211137963315232910",
|
OrgId: Tester.Organisation.ID,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Email: &user.SetHumanEmail{
|
Email: &user.SetHumanEmail{
|
||||||
@@ -260,7 +260,7 @@ func TestServer_AddHumanUser(t *testing.T) {
|
|||||||
&user.AddHumanUserRequest{
|
&user.AddHumanUserRequest{
|
||||||
Organisation: &object.Organisation{
|
Organisation: &object.Organisation{
|
||||||
Org: &object.Organisation_OrgId{
|
Org: &object.Organisation_OrgId{
|
||||||
OrgId: "211137963315232910",
|
OrgId: Tester.Organisation.ID,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Profile: &user.SetHumanProfile{
|
Profile: &user.SetHumanProfile{
|
||||||
|
@@ -13,13 +13,29 @@ type DetailsMsg interface {
|
|||||||
GetDetails() *object.Details
|
GetDetails() *object.Details
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// AssertDetails asserts values in a message's object Details,
|
||||||
|
// if the object Details in expected is a non-nil value.
|
||||||
|
// It targets API v2 messages that have the `GetDetails()` method.
|
||||||
|
//
|
||||||
|
// Dynamically generated values are not compared with expected.
|
||||||
|
// Instead a sanity check is performed.
|
||||||
|
// For the sequence a non-zero value is expected.
|
||||||
|
// The change date has to be now, with a tollerance of 1 second.
|
||||||
|
//
|
||||||
|
// The resource owner is compared with expected and is
|
||||||
|
// therefore the only value that has to be set.
|
||||||
func AssertDetails[D DetailsMsg](t testing.TB, exptected, actual D) {
|
func AssertDetails[D DetailsMsg](t testing.TB, exptected, actual D) {
|
||||||
wantDetails, gotDetails := exptected.GetDetails(), actual.GetDetails()
|
wantDetails, gotDetails := exptected.GetDetails(), actual.GetDetails()
|
||||||
|
if wantDetails == nil {
|
||||||
if wantDetails != nil {
|
assert.Nil(t, gotDetails)
|
||||||
assert.NotZero(t, gotDetails.GetSequence())
|
return
|
||||||
}
|
}
|
||||||
wantCD, gotCD := wantDetails.GetChangeDate().AsTime(), gotDetails.GetChangeDate().AsTime()
|
|
||||||
assert.WithinRange(t, gotCD, wantCD, wantCD.Add(time.Minute))
|
assert.NotZero(t, gotDetails.GetSequence())
|
||||||
|
|
||||||
|
gotCD := gotDetails.GetChangeDate().AsTime()
|
||||||
|
now := time.Now()
|
||||||
|
assert.WithinRange(t, gotCD, now.Add(-time.Second), now.Add(time.Second))
|
||||||
|
|
||||||
assert.Equal(t, wantDetails.GetResourceOwner(), gotDetails.GetResourceOwner())
|
assert.Equal(t, wantDetails.GetResourceOwner(), gotDetails.GetResourceOwner())
|
||||||
}
|
}
|
||||||
|
51
internal/integration/assert_test.go
Normal file
51
internal/integration/assert_test.go
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
package integration
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"google.golang.org/protobuf/types/known/timestamppb"
|
||||||
|
|
||||||
|
object "github.com/zitadel/zitadel/pkg/grpc/object/v2alpha"
|
||||||
|
)
|
||||||
|
|
||||||
|
type myMsg struct {
|
||||||
|
details *object.Details
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m myMsg) GetDetails() *object.Details {
|
||||||
|
return m.details
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAssertDetails(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
exptected myMsg
|
||||||
|
actual myMsg
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "nil",
|
||||||
|
exptected: myMsg{},
|
||||||
|
actual: myMsg{},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "values",
|
||||||
|
exptected: myMsg{
|
||||||
|
details: &object.Details{
|
||||||
|
ResourceOwner: "me",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
actual: myMsg{
|
||||||
|
details: &object.Details{
|
||||||
|
Sequence: 123,
|
||||||
|
ChangeDate: timestamppb.Now(),
|
||||||
|
ResourceOwner: "me",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
AssertDetails(t, tt.exptected, tt.actual)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
@@ -3,7 +3,7 @@ version: '3.8'
|
|||||||
services:
|
services:
|
||||||
cockroach:
|
cockroach:
|
||||||
extends:
|
extends:
|
||||||
file: '../localhost/docker-compose.yaml'
|
file: '../../../e2e/config/localhost/docker-compose.yaml'
|
||||||
service: 'db'
|
service: 'db'
|
||||||
|
|
||||||
postgres:
|
postgres:
|
@@ -41,6 +41,11 @@ var (
|
|||||||
postgresYAML []byte
|
postgresYAML []byte
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// UserType provides constants that give
|
||||||
|
// a short explinanation with the purpose
|
||||||
|
// a serverice user.
|
||||||
|
// This allows to pre-create users with
|
||||||
|
// different permissions and reuse them.
|
||||||
type UserType int
|
type UserType int
|
||||||
|
|
||||||
//go:generate stringer -type=UserType
|
//go:generate stringer -type=UserType
|
||||||
@@ -49,11 +54,13 @@ const (
|
|||||||
OrgOwner
|
OrgOwner
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// User information with a Personal Access Token.
|
||||||
type User struct {
|
type User struct {
|
||||||
*query.User
|
*query.User
|
||||||
Token string
|
Token string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Tester is a Zitadel server and client with all resources available for testing.
|
||||||
type Tester struct {
|
type Tester struct {
|
||||||
*start.Server
|
*start.Server
|
||||||
|
|
||||||
@@ -113,7 +120,7 @@ func (s *Tester) pollHealth(ctx context.Context) (err error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const (
|
const (
|
||||||
SystemUser = "integration1"
|
SystemUser = "integration"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (s *Tester) createSystemUser(ctx context.Context) {
|
func (s *Tester) createSystemUser(ctx context.Context) {
|
||||||
@@ -169,6 +176,7 @@ func (s *Tester) WithSystemAuthorization(ctx context.Context, u UserType) contex
|
|||||||
return metadata.AppendToOutgoingContext(ctx, "Authorization", fmt.Sprintf("Bearer %s", s.Users[u].Token))
|
return metadata.AppendToOutgoingContext(ctx, "Authorization", fmt.Sprintf("Bearer %s", s.Users[u].Token))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Done send an interrupt signal to cleanly shutdown the server.
|
||||||
func (s *Tester) Done() {
|
func (s *Tester) Done() {
|
||||||
err := s.GRPCClientConn.Close()
|
err := s.GRPCClientConn.Close()
|
||||||
logging.OnError(err).Error("integration tester client close")
|
logging.OnError(err).Error("integration tester client close")
|
||||||
@@ -177,6 +185,20 @@ func (s *Tester) Done() {
|
|||||||
s.wg.Wait()
|
s.wg.Wait()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NewTester start a new Zitadel server by passing the default commandline.
|
||||||
|
// The server will listen on the configured port.
|
||||||
|
// The database configuration that will be used can be set by the
|
||||||
|
// INTEGRATION_DB_FLAVOR environment variable and can have the values "cockroach"
|
||||||
|
// or "postgres". Defaults to "cockroach".
|
||||||
|
//
|
||||||
|
// The deault Instance and Organisation are read from the DB and system
|
||||||
|
// users are created as needed.
|
||||||
|
//
|
||||||
|
// After the server is started, a [grpc.ClientConn] will be created and
|
||||||
|
// the server is polled for it's health status.
|
||||||
|
//
|
||||||
|
// Note: the database must already be setup and intialized before
|
||||||
|
// using NewTester. See the CONTRIBUTING.md document for details.
|
||||||
func NewTester(ctx context.Context) *Tester {
|
func NewTester(ctx context.Context) *Tester {
|
||||||
args := strings.Split(commandLine, " ")
|
args := strings.Split(commandLine, " ")
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user