Skip to content

Commit

Permalink
Merge pull request #837 from ClickHouse/736-readonly-tests
Browse files Browse the repository at this point in the history
Read only user integration tests
  • Loading branch information
jkaflik authored Dec 9, 2022
2 parents d6f142a + c250764 commit 226a902
Show file tree
Hide file tree
Showing 2 changed files with 231 additions and 0 deletions.
125 changes: 125 additions & 0 deletions tests/read_only_user_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
// Licensed to ClickHouse, Inc. under one or more contributor
// license agreements. See the NOTICE file distributed with
// this work for additional information regarding copyright
// ownership. ClickHouse, Inc. licenses this file to you under
// the Apache License, Version 2.0 (the "License"); you may
// not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.

package tests

import (
"context"
"github.com/ClickHouse/clickhouse-go/v2/lib/driver"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"testing"
)

func TestReadOnlyUser(t *testing.T) {
readQueryCases := []struct {
name string
query string
assertRowFn func(row driver.Row)
}{
{
name: "select from table",
query: "SELECT sum(Col1) FROM test_readonly_user",
assertRowFn: func(row driver.Row) {
var expectedValue uint64
err := row.Scan(&expectedValue)
assert.NoError(t, err)

assert.Equal(t, expectedValue, uint64(5))
},
},
}

writeQueryCases := []struct {
name string
query string
}{
{
name: "create table",
query: "CREATE TABLE some_table (Col1 UInt8) Engine MergeTree() ORDER BY tuple()",
},
{
name: "insert into table",
query: "INSERT INTO test_readonly_user VALUES (0)"},
{
name: "drop table",
query: "DROP TABLE test_readonly_user",
},
}

setSettingQueries := []struct {
name string
query string
}{
{
name: "set setting",
query: "SET log_queries = 0",
},
}

ctx := context.Background()

env, err := GetTestEnvironment(testSet)
require.NoError(t, err)
client, err := testClientWithDefaultSettings(env)
require.NoError(t, err)
defer client.Close()

require.NoError(t, createSimpleTable(client, "test_readonly_user"))
defer dropTable(client, "test_readonly_user")
require.NoError(t, client.Exec(ctx, `
INSERT INTO test_readonly_user VALUES (5)
`))

username, password, err := createUserWithReadOnlySetting(client, env.Database, readOnlyRead)
require.NoError(t, err)
defer func() {
require.NoError(t, dropUser(client, username))
}()

roEnv := env
roEnv.Username = username
roEnv.Password = password

roClient, err := testClientWithDefaultOptions(roEnv, nil)
require.NoError(t, err)
defer roClient.Close()

for _, testCase := range readQueryCases {
t.Run(testCase.name, func(t *testing.T) {
row := roClient.QueryRow(ctx, testCase.query)
assert.NoError(t, err)
testCase.assertRowFn(row)
})
}

for _, testCase := range writeQueryCases {
t.Run(testCase.name, func(t *testing.T) {
actualErr := roClient.Exec(ctx, testCase.query)

assert.ErrorContains(t, actualErr, "Cannot execute query in readonly mode")
})
}

for _, testCase := range setSettingQueries {
t.Run(testCase.name, func(t *testing.T) {
actualErr := roClient.Exec(ctx, testCase.query)

assert.ErrorContains(t, actualErr, "setting in readonly mode")
})
}
}
106 changes: 106 additions & 0 deletions tests/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -245,6 +245,61 @@ func GetExternalTestEnvironment(testSet string) (ClickHouseTestEnvironment, erro
return env, nil
}

func clientOptionsFromEnv(env ClickHouseTestEnvironment, settings clickhouse.Settings) clickhouse.Options {
timeout, err := strconv.Atoi(GetEnv("CLICKHOUSE_DIAL_TIMEOUT", "10"))
if err != nil {
timeout = 10
}

useSSL, err := strconv.ParseBool(GetEnv("CLICKHOUSE_USE_SSL", "false"))
if err != nil {
panic(err)
}
port := env.Port
var tlsConfig *tls.Config
if useSSL {
tlsConfig = &tls.Config{}
port = env.SslPort
}

return clickhouse.Options{
Addr: []string{fmt.Sprintf("%s:%d", env.Host, port)},
Settings: settings,
Auth: clickhouse.Auth{
Database: env.Database,
Username: env.Username,
Password: env.Password,
},
DialTimeout: time.Duration(timeout) * time.Second,
TLS: tlsConfig,
Compression: &clickhouse.Compression{
Method: clickhouse.CompressionLZ4,
},
}
}

func testClientWithDefaultOptions(env ClickHouseTestEnvironment, settings clickhouse.Settings) (driver.Conn, error) {
opts := clientOptionsFromEnv(env, settings)
return clickhouse.Open(&opts)
}

func testClientWithDefaultSettings(env ClickHouseTestEnvironment) (driver.Conn, error) {
settings := clickhouse.Settings{}

if proto.CheckMinVersion(proto.Version{
Major: 22,
Minor: 8,
Patch: 0,
}, env.Version) {
settings["database_replicated_enforce_synchronous_settings"] = "1"
}
settings["insert_quorum"], _ = strconv.Atoi(GetEnv("CLICKHOUSE_QUORUM_INSERT", "1"))
settings["insert_quorum_parallel"] = 0
settings["select_sequential_consistency"] = 1

return testClientWithDefaultOptions(env, settings)
}

func GetConnection(testSet string, settings clickhouse.Settings, tlsConfig *tls.Config, compression *clickhouse.Compression) (driver.Conn, error) {
env, err := GetTestEnvironment(testSet)
if err != nil {
Expand Down Expand Up @@ -331,6 +386,57 @@ func CreateDatabase(testSet string) error {
return conn.Exec(context.Background(), fmt.Sprintf("CREATE DATABASE `%s`", env.Database))
}

const (
readOnlyReadWriteChangeSettings = 0
readOnlyRead = 1
readOnlyReadChangeSettings = 2
)

func createUserWithReadOnlySetting(conn driver.Conn, defaultDatabase string, readOnlyType int) (username, password string, err error) {
username = fmt.Sprintf("readonly_user_%s", RandAsciiString(6))
password = RandAsciiString(6)

createUserQuery := fmt.Sprintf(`
CREATE USER IF NOT EXISTS %s
IDENTIFIED BY '%s'
DEFAULT DATABASE "%s"
SETTINGS readonly = %d
`, username, password, defaultDatabase, readOnlyType)
if err := conn.Exec(context.Background(), createUserQuery); err != nil {
return "", "", err
}

grantQuery := fmt.Sprintf(`
GRANT SELECT, INSERT, CREATE TABLE, DROP TABLE
ON "%s".*
TO %s
`, defaultDatabase, username)

return username, password, conn.Exec(context.Background(), grantQuery)
}

func dropUser(conn driver.Conn, username string) error {
query := fmt.Sprintf(`
DROP USER IF EXISTS %s
`, username)

return conn.Exec(context.Background(), query)
}

func createSimpleTable(client driver.Conn, table string) error {
return client.Exec(context.Background(), fmt.Sprintf(`
CREATE TABLE %s (
Col1 UInt8
) Engine MergeTree() ORDER BY tuple()
`, table))
}

func dropTable(client driver.Conn, table string) error {
return client.Exec(context.Background(), fmt.Sprintf(`
DROP TABLE %s
`, table))
}

func getDatabaseName(testSet string) string {
return fmt.Sprintf("clickhouse-go-%s-%s-%d", testSet, testUUID, testTimestamp)
}
Expand Down

0 comments on commit 226a902

Please sign in to comment.