* Fix down migrations * Udpates due to merge * Sentinel values only have to be greater than 0 * Create credential related sdk structs and methods. * Post merge interface updating. * Removing scratch code that was not cleaned up. * updates * Convert client certificate and client certificate keys into pem blocks and validate. * Add newline at end of file. * Faster check for "s" at the end of a resource name when generating from templates. * make fmt * updates * Add dynamic credentials to a session * Refactor: move contents of file * updates * Updates * Add InvalidDynamicCredential error * Assign credentials * Allow updating client certificates seperate from the client certificate key. * Replace application purpose string with enum value * PR feedback / fix go test . * Delete dead code * Use common view * Refactor: extract interface and rename method * Refactor: move code to eliminate privPurpLibrary * Add comment to requestMap * Refactor: do not export methods on an unexported struct * Update internal/db/schema/migrations/postgres/10/03_vault_credential.up.sql Co-authored-by: Michael Gaffney <mgaffney@users.noreply.github.com> * Add comments to credential Purpose constants * Reorder switch statements to handle errors first * PR feedback * update * fix merge * Organizing the scheduler code and extract the individual store creation test helper function. * Run 'make fmt' * Create certificate when creating store. Allow vault address to be passed in as option * Refactor: rename database views and associated structs This change renames two database views and associated go structs to use a consistent naming pattern. Views ending in 'private' contain encrypted credentials needed for connecting to Vault. Views ending in 'public' mirror the 'private' views but do not include any encrypted values. This change does not include any changes to functionality or behavior. * Adding SAD target operations for credential libraries to the SDK. * Add status field and enumeration table for credential status * Refactor: rename Status to TokenStatus * Add status to vault credentials * Creating the template for the CLI and helper functions. * Add private database view for vault credentials * Add scope_id to view and cleanup TODO comment * Reformat embedded sql queries * Add vault client helpers * updates * white space * Add LookupLease test * CLI commands for Store work. Renamed fields in proto w/ vault prefix. * Test issue credentials with client TLS * Add additional checks to vault ping * Add an error for vault token capabilities and create a VaultToken error kind * Add support for working with Vault policies * Use Vault 1.7.2 for testing * Add testing helper for creating a vault client with a non-root token * Fix Vault renew lease test The Vault renew lease endpoint accepts an optional duration parameter for requesting the number of seconds to extend the lease by. However, Vault is not required to honor this request. * Add a var for the required capabilities of a boundary Vault token * Enhance the test helper for adding a policy to Vault * Allow the test role in postgresql to revoke credentials * Remove out of date comments * Enhance Vault test helpers for the database secrets engine * Reformat comments * Add a method for getting the capabilities of the current Vault token * Add revoke lease to internal Vault client * Reformat: combine err return line with check for nil line * Remove redundant checks in test code Remove checks in test code that are redundant with checks in the test harness. * Refactor CreateToken test helper to return the secret and the token * Fix build failures caused by signature change in vault.CreateToken * Fix Vault version and Vault API link in comments * ran `make gen` after merging from mgaffney-vault. * Remove Vault prefix from attribute fields in API, create a base CLI option for adding field prefixes. format now uses that prefix for error reporting. * Template now allows attaching attribute field prefixes. * Credential store uses PrefixAttributeFieldErrorsWithSubactionPrefix. * Fixing naming of vault flags and helper flags. * Updating more references to generated api methods. * Make the capabilities String method more readable * Add comment outlining PrintApiError usage of Options. * Refactor test to move declarations closer to first use * Fix comment * Implement revoke method on vault repository * Tests now verify certs, CA, and tokens, addresses can be updated in a store. * Added test for creating store with pk/cert in same field. * Allo updating certificates seperate from private key. Only contact backing vault service when updating token. * Fixing bad tests and removing unused code. * Adding tests for various ways of updating tokens. * Add private credential * Add token revocation job and cred renewal/revocation job * Regen after merge. * Fixing field mask related merge error. * Create Library CLI. Removes vault prefix from path. Adds a WithoutCleanup testOption for vault. * Set all credentials to revoked when a token is revoked * Fixing missed renaming update. * fix copy/paste error. * Fixing code generation errors after merge. * make gen after merge. * Update internal/cmd/commands/credentialstorescmd/vault_funcs.go Co-authored-by: Michael Gaffney <mgaffney@users.noreply.github.com> * Update internal/cmd/commands/credentialstorescmd/vault_funcs.go Co-authored-by: Michael Gaffney <mgaffney@users.noreply.github.com> * Update internal/cmd/commands/credentialstorescmd/vault_funcs.go Co-authored-by: Michael Gaffney <mgaffney@users.noreply.github.com> * Refactor: move migrations from 10 to 12 * Removing logic from transaction. * wrapping client errors in the calling op for CredentialStore's client() method. * Redact token * redact client key * updates * Add trigger to revoke credentials when session is canceled or terminated * Fixing some help text for some flags. * Add ability to load values from files (file://) or environment variables (env://). * Updating comments * fix bad merge * feedback * Adding issued credentials to sessions. * Error on capabilities * updates * Expose NewVaulTestServer * Add godoc * Adding generated SessionCredential struct for API. * Progress on building handler test for Authorize Session. * feedback * Update policies * Add 'revoke' as a status for Vault Tokens * Add delete_time to vault credential store * Create dynamic credentials at session creation time. * Tests pass with credentials attached to AuthorizedSession result. * Only call issue if there are credentials to issue. * Fixing output only issues. Adding type to credential library. * Fix test to clear un/pw generated. confirm generated un/pw changes from 1 authorize session call to another. * Update lookupTokenError * Add delete_time to private credential store * Refactor: move private store to separate file * Fixing tests for AuthorizeSession and verifying error cases. * Only create the credential repo if there are credentials to issue. * Rename Library field to CredentialLibrary. * Fixing missing change of Library to CredentialLibrary. * Removing Default SDK functions for fields which are required and update CLI to match. * Replace resource specific id variables with "id" and the input parmater for paths to be just a single string and the resource name as it should show up in the path. * Soft delete vault credential stores * Prevent new SQL functions from silently overwriting existing functions * Rename "Hmac" to "HMAC" in the human readable cli response table. * Fix tests * Add vault database cleanup jobs * Add ability to set the token period in the vault test harness * updates * Add not_null_columns func test * remove unused struct * Up no output timeout * Validate current vault token on addr update * Fix client cert change test * add cert test * Update revocation job where * Fix comment * Use Vault's default port in CLI help strings * Remove down migrations * Increase migration file numbers by 1 * Run make migrations * Move migrations from 12 to 10 * Increase Circle CI timeout for acceptance tests * Fix spelling mistake in error messages * ASD cmds for Target and returning credentials to AuthSession request (#1338) * Add Add/Set/Remove commands for libraries on Targets. Return credentials on AuthorizeSession request. Co-authored-by: Michael Gaffney <mgaffney@users.noreply.github.com> Co-authored-by: Louis Ruch <louisruch@gmail.com> Co-authored-by: Todd Knight <T.Alan.Knight@gmail.com> Co-authored-by: Jeff Mitchell <jeffrey.mitchell@gmail.com>
1125 lines
25 KiB
Go
1125 lines
25 KiB
Go
package db
|
|
|
|
import (
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/golang-sql/civil"
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
func TestDomain_Ids(t *testing.T) {
|
|
const (
|
|
createTableBase = `
|
|
create table if not exists test_table_{{rep}}_id (
|
|
id bigint generated always as identity primary key,
|
|
{{rep}}_id wt_{{rep}}_id
|
|
);
|
|
`
|
|
insertBase = `
|
|
insert into test_table_{{rep}}_id ({{rep}}_id)
|
|
values ($1)
|
|
returning id;
|
|
`
|
|
)
|
|
|
|
conn, _ := TestSetup(t, "postgres")
|
|
db := conn.DB()
|
|
|
|
for _, typ := range []string{"public", "private", "role", "scope"} {
|
|
createTable := strings.Replace(createTableBase, "{{rep}}", typ, -1)
|
|
insert := strings.Replace(insertBase, "{{rep}}", typ, -1)
|
|
|
|
if _, err := db.Exec(createTable); err != nil {
|
|
t.Fatalf("query: \n%s\n error: %s", createTable, err)
|
|
}
|
|
|
|
failTests := []struct {
|
|
value string
|
|
exception string
|
|
}{
|
|
{
|
|
" ",
|
|
"",
|
|
},
|
|
{
|
|
"bar",
|
|
"",
|
|
},
|
|
{
|
|
"00000009",
|
|
"",
|
|
},
|
|
{
|
|
"global",
|
|
"scope",
|
|
},
|
|
}
|
|
for _, tt := range failTests {
|
|
t.Run(tt.value, func(t *testing.T) {
|
|
t.Logf("insert value: %q", tt.value)
|
|
_, err := db.Query(insert, tt.value)
|
|
switch {
|
|
case err == nil && tt.exception != typ:
|
|
t.Errorf("want error, got no error for inserting public_id: %s", tt.value)
|
|
case err == nil && tt.exception == typ:
|
|
// All good
|
|
case err != nil && tt.exception != typ:
|
|
// All good
|
|
default:
|
|
t.Fatal("unhandled")
|
|
}
|
|
})
|
|
}
|
|
|
|
okTests := []string{
|
|
"l1Ocw0TpHn800CekIxIXlmQqRDgFDfYl",
|
|
"00000000010000000002000000000312",
|
|
"00000000010000000002000000000032",
|
|
"12345678901234567890123456789012",
|
|
"ec2_12345678901234567890123456789012",
|
|
"prj_12345678901234567890123456789012",
|
|
}
|
|
for _, tt := range okTests {
|
|
value := tt
|
|
t.Run(tt, func(t *testing.T) {
|
|
t.Logf("insert value: %q", value)
|
|
if _, err := db.Query(insert, value); err != nil {
|
|
t.Errorf("%s: want no error, got error %v", value, err)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestDomain_TagPairs(t *testing.T) {
|
|
const (
|
|
createTable = `
|
|
create table if not exists test_table (
|
|
pair wt_tagpair primary key
|
|
);
|
|
`
|
|
insert = `
|
|
insert into test_table
|
|
values ($1);
|
|
`
|
|
)
|
|
|
|
conn, _ := TestSetup(t, "postgres")
|
|
db := conn.DB()
|
|
|
|
if _, err := db.Exec(createTable); err != nil {
|
|
t.Fatalf("query: \n%s\n error: %s", createTable, err)
|
|
}
|
|
|
|
tests := []struct {
|
|
value string
|
|
fail bool
|
|
}{
|
|
{
|
|
" ",
|
|
true,
|
|
},
|
|
{
|
|
"00000009",
|
|
false,
|
|
},
|
|
{
|
|
"ABC123",
|
|
true,
|
|
},
|
|
{
|
|
"092h3oiuansdfh98wh3piornasiopudhfp98aw384rhfaouisdhnfios",
|
|
false,
|
|
},
|
|
{
|
|
strings.Repeat("1234567890", 51),
|
|
false,
|
|
},
|
|
{
|
|
strings.Repeat("1234567890", 52),
|
|
true,
|
|
},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.value, func(t *testing.T) {
|
|
// t.Logf("insert value: %q", tt.value)
|
|
_, err := db.Query(insert, tt.value)
|
|
switch {
|
|
case err == nil && tt.fail:
|
|
t.Errorf("want error, got no error for inserting value: %s", tt.value)
|
|
case err == nil && !tt.fail:
|
|
// All good
|
|
case err != nil && tt.fail:
|
|
// All good
|
|
default:
|
|
t.Errorf("expected no error, got error: %s", err)
|
|
}
|
|
})
|
|
}
|
|
t.Run("null", func(t *testing.T) {
|
|
_, err := db.Query("insert into test_table values (null);")
|
|
assert.Contains(t, err.Error(), "violates check constraint")
|
|
})
|
|
}
|
|
|
|
func TestDomain_Bexprfilter(t *testing.T) {
|
|
const (
|
|
createTable = `
|
|
create table if not exists test_table (
|
|
filter wt_bexprfilter
|
|
);
|
|
`
|
|
insert = `
|
|
insert into test_table
|
|
values ($1);
|
|
`
|
|
)
|
|
|
|
conn, _ := TestSetup(t, "postgres")
|
|
db := conn.DB()
|
|
|
|
if _, err := db.Exec(createTable); err != nil {
|
|
t.Fatalf("query: \n%s\n error: %s", createTable, err)
|
|
}
|
|
|
|
tests := []struct {
|
|
value string
|
|
fail bool
|
|
}{
|
|
{
|
|
" ",
|
|
true,
|
|
},
|
|
{
|
|
"00000009",
|
|
false,
|
|
},
|
|
{
|
|
"ABC123",
|
|
false,
|
|
},
|
|
{
|
|
`"/foo/bar" in "/values"`,
|
|
false,
|
|
},
|
|
{
|
|
strings.Repeat("1234567890", 204),
|
|
false,
|
|
},
|
|
{
|
|
strings.Repeat("1234567890", 205),
|
|
true,
|
|
},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.value, func(t *testing.T) {
|
|
// t.Logf("insert value: %q", tt.value)
|
|
_, err := db.Query(insert, tt.value)
|
|
switch {
|
|
case err == nil && tt.fail:
|
|
t.Errorf("want error, got no error for inserting value: %s", tt.value)
|
|
case err == nil && !tt.fail:
|
|
// All good
|
|
case err != nil && tt.fail:
|
|
// All good
|
|
default:
|
|
t.Errorf("expected no error, got error: %s", err)
|
|
}
|
|
})
|
|
}
|
|
t.Run("null", func(t *testing.T) {
|
|
_, err := db.Query("insert into test_table values (null);")
|
|
assert.NoError(t, err)
|
|
})
|
|
}
|
|
|
|
func TestDomain_Timestamp(t *testing.T) {
|
|
const (
|
|
createTable = `
|
|
create table if not exists test_table_timestamp (
|
|
id bigint generated always as identity primary key,
|
|
name text,
|
|
good_time wt_timestamp,
|
|
bad_time timestamp default current_timestamp
|
|
);
|
|
`
|
|
insert = `
|
|
insert into test_table_timestamp (name)
|
|
values ($1)
|
|
returning id;
|
|
`
|
|
)
|
|
|
|
conn, _ := TestSetup(t, "postgres")
|
|
db := conn.DB()
|
|
if _, err := db.Exec(createTable); err != nil {
|
|
t.Fatalf("query: \n%s\n error: %s", createTable, err)
|
|
}
|
|
|
|
if _, err := db.Exec(insert, "foo"); err != nil {
|
|
t.Fatalf("want no error, got error %v", err)
|
|
}
|
|
|
|
if _, err := db.Query("select extract(timezone from good_time) from test_table_timestamp;"); err != nil {
|
|
t.Errorf("want no error, got error %v", err)
|
|
}
|
|
|
|
if _, err := db.Query("select extract(timezone from bad_time) from test_table_timestamp;"); err == nil {
|
|
t.Errorf("want error, got no error")
|
|
}
|
|
}
|
|
|
|
func TestDomain_update_time_column(t *testing.T) {
|
|
assert := assert.New(t)
|
|
|
|
const (
|
|
createTable = `
|
|
create table if not exists test_update_time_column (
|
|
id bigint generated always as identity primary key,
|
|
name text,
|
|
update_time wt_timestamp
|
|
);
|
|
`
|
|
addTriggers = `
|
|
create trigger
|
|
test_update_time_column
|
|
before
|
|
update on test_update_time_column
|
|
for each row execute procedure update_time_column();
|
|
`
|
|
countQuery = `
|
|
select count(*) from test_update_time_column
|
|
`
|
|
query = `
|
|
select update_time from test_update_time_column where id = $1
|
|
`
|
|
insert = `
|
|
insert into test_update_time_column (name)
|
|
values ('bob')
|
|
returning id;
|
|
`
|
|
// no where clause intentionally, since it's not needed for the test
|
|
update = `
|
|
update test_update_time_column
|
|
set name = 'alice';
|
|
`
|
|
|
|
// no where clause intentionally, since it's not needed for the test
|
|
bad_update = `
|
|
update test_update_time_column
|
|
set update_time = now() - interval '3 days';
|
|
`
|
|
// no where clause intentionally, since it's not needed for the test
|
|
bad_update_null = `
|
|
update test_update_time_column
|
|
set update_time = null;
|
|
`
|
|
)
|
|
|
|
conn, _ := TestSetup(t, "postgres")
|
|
db := conn.DB()
|
|
_, err := db.Exec(createTable)
|
|
assert.NoError(err)
|
|
|
|
_, err = db.Exec(addTriggers)
|
|
assert.NoError(err)
|
|
|
|
var id int
|
|
err = db.QueryRow(insert).Scan(&id)
|
|
assert.NoError(err)
|
|
|
|
var origTime time.Time
|
|
err = db.QueryRow(query, id).Scan(&origTime)
|
|
assert.NoError(err)
|
|
orig := civil.DateTimeOf(origTime)
|
|
|
|
var count int
|
|
err = db.QueryRow(countQuery).Scan(&count)
|
|
assert.NoError(err)
|
|
assert.Equal(1, count)
|
|
|
|
_, err = db.Exec(update)
|
|
assert.NoError(err)
|
|
|
|
_, err = db.Exec(bad_update)
|
|
assert.NoError(err)
|
|
|
|
var updateTime time.Time
|
|
err = db.QueryRow(query, id).Scan(&updateTime)
|
|
assert.NoError(err)
|
|
updated := civil.DateTimeOf(updateTime)
|
|
assert.True(updated.After(orig))
|
|
|
|
_, err = db.Exec(bad_update_null)
|
|
assert.NoError(err)
|
|
|
|
var nextUpdateTime time.Time
|
|
err = db.QueryRow(query, id).Scan(&nextUpdateTime)
|
|
assert.NoError(err)
|
|
nextUpdated := civil.DateTimeOf(nextUpdateTime)
|
|
assert.True(nextUpdated.After(updated))
|
|
}
|
|
|
|
func TestDomain_wt_version(t *testing.T) {
|
|
const (
|
|
createTable = `
|
|
create table if not exists test_table_wt_version(
|
|
public_id bigint generated always as identity primary key,
|
|
name text,
|
|
version wt_version
|
|
);
|
|
`
|
|
addTrigger = `
|
|
create trigger
|
|
update_version_column
|
|
after update on test_table_wt_version
|
|
for each row execute procedure update_version_column();
|
|
`
|
|
insert = `
|
|
insert into test_table_wt_version (name)
|
|
values ($1)
|
|
returning public_id;
|
|
`
|
|
update = `update test_table_wt_version set name = $1 where public_id = $2;`
|
|
|
|
updateVersion = `update test_table_wt_version set version = $1 where public_id = $2;`
|
|
|
|
search = `select version from test_table_wt_version where public_id = $1`
|
|
)
|
|
conn, _ := TestSetup(t, "postgres")
|
|
assert, require := assert.New(t), require.New(t)
|
|
|
|
db := conn.DB()
|
|
_, err := db.Exec(createTable)
|
|
require.NoError(err)
|
|
|
|
_, err = db.Exec(addTrigger)
|
|
require.NoError(err)
|
|
|
|
var id int
|
|
err = db.QueryRow(insert, "alice").Scan(&id)
|
|
require.NoError(err)
|
|
require.NotEmpty(id)
|
|
|
|
var v int
|
|
err = db.QueryRow(search, id).Scan(&v)
|
|
require.NoError(err)
|
|
assert.Equal(1, v)
|
|
|
|
_, err = db.Exec(update, "bob", id)
|
|
require.NoError(err)
|
|
err = db.QueryRow(search, id).Scan(&v)
|
|
require.NoError(err)
|
|
assert.Equal(2, v)
|
|
|
|
_, err = db.Exec(updateVersion, 22, id)
|
|
require.NoError(err)
|
|
err = db.QueryRow(search, id).Scan(&v)
|
|
require.NoError(err)
|
|
assert.Equal(3, v)
|
|
}
|
|
|
|
func TestDomain_wt_version_with_private_id(t *testing.T) {
|
|
const (
|
|
createTable = `
|
|
create table if not exists test_table_wt_version(
|
|
private_id bigint generated always as identity primary key,
|
|
name text,
|
|
version wt_version
|
|
);
|
|
`
|
|
addTrigger = `
|
|
create trigger
|
|
update_version_column
|
|
after update on test_table_wt_version
|
|
for each row execute procedure update_version_column('private_id');
|
|
`
|
|
insert = `
|
|
insert into test_table_wt_version (name)
|
|
values ($1)
|
|
returning private_id;
|
|
`
|
|
update = `update test_table_wt_version set name = $1 where private_id = $2;`
|
|
|
|
updateVersion = `update test_table_wt_version set version = $1 where private_id = $2;`
|
|
|
|
search = `select version from test_table_wt_version where private_id = $1`
|
|
)
|
|
conn, _ := TestSetup(t, "postgres")
|
|
assert, require := assert.New(t), require.New(t)
|
|
|
|
db := conn.DB()
|
|
_, err := db.Exec(createTable)
|
|
require.NoError(err)
|
|
|
|
_, err = db.Exec(addTrigger)
|
|
require.NoError(err)
|
|
|
|
var id int
|
|
err = db.QueryRow(insert, "alice").Scan(&id)
|
|
require.NoError(err)
|
|
require.NotEmpty(id)
|
|
|
|
var v int
|
|
err = db.QueryRow(search, id).Scan(&v)
|
|
require.NoError(err)
|
|
assert.Equal(1, v)
|
|
|
|
_, err = db.Exec(update, "bob", id)
|
|
require.NoError(err)
|
|
err = db.QueryRow(search, id).Scan(&v)
|
|
require.NoError(err)
|
|
assert.Equal(2, v)
|
|
|
|
_, err = db.Exec(updateVersion, 22, id)
|
|
require.NoError(err)
|
|
err = db.QueryRow(search, id).Scan(&v)
|
|
require.NoError(err)
|
|
assert.Equal(3, v)
|
|
}
|
|
|
|
func TestDomain_DefaultUsersExist(t *testing.T) {
|
|
conn, _ := TestSetup(t, "postgres")
|
|
db := conn.DB()
|
|
|
|
for _, val := range []string{"u_anon", "u_auth"} {
|
|
rows, err := db.Query(`select from iam_user where public_id = $1`, val)
|
|
require.NoError(t, err)
|
|
var count int
|
|
for rows.Next() {
|
|
count++
|
|
}
|
|
assert.Equal(t, 1, count)
|
|
}
|
|
}
|
|
|
|
func TestDomain_immutable_columns_func(t *testing.T) {
|
|
const (
|
|
createTable = `
|
|
create table if not exists test_immutable_columns (
|
|
id bigint generated always as identity primary key,
|
|
name text not null,
|
|
create_time wt_timestamp not null,
|
|
updatable text not null
|
|
);
|
|
`
|
|
addTriggers = `
|
|
create trigger
|
|
test_update_immutable_columns
|
|
before
|
|
update on test_immutable_columns
|
|
for each row execute procedure immutable_columns('id', 'name', 'create_time');
|
|
`
|
|
dropTriggers = `drop trigger test_update_immutable_columns on test_immutable_columns`
|
|
|
|
addBadTriggers = `
|
|
create trigger
|
|
test_update_bad_immutable_columns
|
|
before
|
|
update on test_immutable_columns
|
|
for each row execute procedure immutable_columns('id', 'bad_column_name', 'create_time');
|
|
`
|
|
dropBadTriggers = `drop trigger test_update_bad_immutable_columns on test_immutable_columns`
|
|
|
|
query = `
|
|
select id, name, create_time, updatable from test_immutable_columns where id = $1
|
|
`
|
|
countQuery = `
|
|
select count(*) from test_immutable_columns
|
|
`
|
|
insert = `
|
|
insert into test_immutable_columns (name, updatable)
|
|
values ('alice', 'update alice')
|
|
returning id;
|
|
`
|
|
// no where clause intentionally, since it's not needed for the test
|
|
update_updatable = `
|
|
update test_immutable_columns
|
|
set updatable = 'updated bob';
|
|
`
|
|
|
|
// no where clause intentionally, since it's not needed for the test
|
|
bad_update_create_time = `
|
|
update test_immutable_columns
|
|
set create_time = now();
|
|
`
|
|
// no where clause intentionally, since it's not needed for the test
|
|
bad_update_null_create_time = `
|
|
update test_immutable_columns
|
|
set create_time = null;
|
|
`
|
|
|
|
// no where clause intentionally, since it's not needed for the test
|
|
bad_update_id = `
|
|
update test_immutable_columns
|
|
set id = 22;
|
|
`
|
|
// no where clause intentionally, since it's not needed for the test
|
|
bad_update_null_id = `
|
|
update test_immutable_columns
|
|
set id = null;
|
|
`
|
|
)
|
|
|
|
type rowData struct {
|
|
Id int
|
|
CreateTime time.Time
|
|
Name string
|
|
Updatable string
|
|
}
|
|
|
|
conn, _ := TestSetup(t, "postgres")
|
|
db := conn.DB()
|
|
_, err := db.Exec(createTable)
|
|
assert.NoError(t, err)
|
|
|
|
_, err = db.Exec(addTriggers)
|
|
assert.NoError(t, err)
|
|
|
|
var id int
|
|
err = db.QueryRow(insert).Scan(&id)
|
|
assert.NoError(t, err)
|
|
|
|
var orig rowData
|
|
err = db.QueryRow(query, id).Scan(&orig.Id, &orig.Name, &orig.CreateTime, &orig.Updatable)
|
|
assert.NoError(t, err)
|
|
|
|
var count int
|
|
err = db.QueryRow(countQuery).Scan(&count)
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, 1, count)
|
|
|
|
tests := []struct {
|
|
name string
|
|
stmt string
|
|
}{
|
|
{
|
|
name: "update create time",
|
|
stmt: bad_update_create_time,
|
|
},
|
|
{
|
|
name: "null create time",
|
|
stmt: bad_update_null_create_time,
|
|
},
|
|
{
|
|
name: "update id",
|
|
stmt: bad_update_id,
|
|
},
|
|
{
|
|
name: "null id",
|
|
stmt: bad_update_null_id,
|
|
},
|
|
}
|
|
for _, tt := range tests {
|
|
tt := tt
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
assert := assert.New(t)
|
|
_, err = db.Exec(tt.stmt)
|
|
assert.Error(err)
|
|
|
|
var found rowData
|
|
err = db.QueryRow(query, id).Scan(&found.Id, &found.Name, &found.CreateTime, &found.Updatable)
|
|
assert.NoError(err)
|
|
assert.Equal(orig.Id, found.Id)
|
|
assert.Equal(orig.Name, found.Name)
|
|
assert.Equal(orig.CreateTime, found.CreateTime)
|
|
})
|
|
}
|
|
t.Run("updatable", func(t *testing.T) {
|
|
assert, require := assert.New(t), require.New(t)
|
|
_, err = db.Exec(update_updatable)
|
|
require.NoError(err)
|
|
var found rowData
|
|
err = db.QueryRow(query, id).Scan(&found.Id, &found.Name, &found.CreateTime, &found.Updatable)
|
|
require.NoError(err)
|
|
assert.NotEqual(orig.Updatable, found.Updatable)
|
|
})
|
|
|
|
t.Run("bad column", func(t *testing.T) {
|
|
assert, require := assert.New(t), require.New(t)
|
|
|
|
t.Cleanup(func() {
|
|
// reset the update trigger to what they were before this test
|
|
// started.
|
|
_, err = db.Exec(dropBadTriggers)
|
|
require.NoError(err)
|
|
|
|
_, err = db.Exec(addTriggers)
|
|
assert.NoError(err)
|
|
})
|
|
_, err = db.Exec(dropTriggers)
|
|
require.NoError(err)
|
|
|
|
_, err = db.Exec(addBadTriggers)
|
|
require.NoError(err)
|
|
|
|
_, err = db.Exec(update_updatable)
|
|
require.Error(err)
|
|
assert.Containsf(err.Error(), `pq: column "bad_column_name" not found`, "unexpected error msg: %s", err.Error())
|
|
|
|
var found rowData
|
|
err = db.QueryRow(query, id).Scan(&found.Id, &found.Name, &found.CreateTime, &found.Updatable)
|
|
assert.NoError(err)
|
|
assert.Equal(orig.Id, found.Id)
|
|
assert.Equal(orig.Name, found.Name)
|
|
assert.Equal(orig.CreateTime, found.CreateTime)
|
|
})
|
|
}
|
|
|
|
func TestDomain_wt_url(t *testing.T) {
|
|
const (
|
|
createTable = `
|
|
create table if not exists test_table_wt_url (
|
|
id bigint generated always as identity primary key,
|
|
url wt_url
|
|
);
|
|
`
|
|
insert = `
|
|
insert into test_table_wt_url(url)
|
|
values ($1)
|
|
returning id;
|
|
`
|
|
)
|
|
|
|
conn, _ := TestSetup(t, "postgres")
|
|
db := conn.DB()
|
|
|
|
if _, err := db.Exec(createTable); err != nil {
|
|
t.Fatalf("query: \n%s\n error: %s", createTable, err)
|
|
}
|
|
|
|
failTests := []struct {
|
|
value string
|
|
exception string
|
|
}{
|
|
{"http://", ""},
|
|
{"htt://", "wt_url_invalid_protocol"},
|
|
{"ht", "wt_url_invalid_protocol"},
|
|
{"http://" + strings.Repeat("a", 4001), "wt_url_too_long"},
|
|
}
|
|
for _, tt := range failTests {
|
|
t.Run(tt.value, func(t *testing.T) {
|
|
assert, require := assert.New(t), require.New(t)
|
|
|
|
t.Logf("insert value: %q", tt.value)
|
|
_, err := db.Query(insert, tt.value)
|
|
if tt.exception != "" {
|
|
require.Error(err)
|
|
assert.Containsf(err.Error(), tt.exception, "missing %s from err: %s", tt.exception, err.Error())
|
|
return
|
|
}
|
|
require.NoError(err)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestDomain_wt_email(t *testing.T) {
|
|
const (
|
|
createTable = `
|
|
create table if not exists test_table_wt_email (
|
|
id bigint generated always as identity primary key,
|
|
email wt_email
|
|
);
|
|
`
|
|
insert = `
|
|
insert into test_table_wt_email(email)
|
|
values ($1)
|
|
returning id;
|
|
`
|
|
)
|
|
|
|
conn, _ := TestSetup(t, "postgres")
|
|
db := conn.DB()
|
|
|
|
if _, err := db.Exec(createTable); err != nil {
|
|
t.Fatalf("query: \n%s\n error: %s", createTable, err)
|
|
}
|
|
|
|
failTests := []struct {
|
|
value string
|
|
exception string
|
|
}{
|
|
{" ", "wt_email_too_short"},
|
|
{"1" + strings.Repeat("1", 320), "wt_email_too_long"},
|
|
{"alice@bob.com", ""},
|
|
}
|
|
for _, tt := range failTests {
|
|
t.Run(tt.value, func(t *testing.T) {
|
|
assert, require := assert.New(t), require.New(t)
|
|
|
|
t.Logf("insert value: %q", tt.value)
|
|
_, err := db.Query(insert, tt.value)
|
|
if tt.exception != "" {
|
|
require.Error(err)
|
|
assert.Containsf(err.Error(), tt.exception, "missing %s from err: %s", tt.exception, err.Error())
|
|
return
|
|
}
|
|
require.NoError(err)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestDomain_wt_full_name(t *testing.T) {
|
|
const (
|
|
createTable = `
|
|
create table if not exists test_table_wt_full_name (
|
|
id bigint generated always as identity primary key,
|
|
full_name wt_full_name
|
|
);
|
|
`
|
|
insert = `
|
|
insert into test_table_wt_full_name(full_name)
|
|
values ($1)
|
|
returning id;
|
|
`
|
|
)
|
|
|
|
conn, _ := TestSetup(t, "postgres")
|
|
db := conn.DB()
|
|
|
|
if _, err := db.Exec(createTable); err != nil {
|
|
t.Fatalf("query: \n%s\n error: %s", createTable, err)
|
|
}
|
|
|
|
failTests := []struct {
|
|
value string
|
|
exception string
|
|
}{
|
|
{" ", "wt_full_name_too_short"},
|
|
{"1" + strings.Repeat("1", 512), "wt_full_name_too_long"},
|
|
{"alice and bob's dinner", ""},
|
|
}
|
|
for _, tt := range failTests {
|
|
t.Run(tt.value, func(t *testing.T) {
|
|
assert, require := assert.New(t), require.New(t)
|
|
|
|
t.Logf("insert value: %q", tt.value)
|
|
_, err := db.Query(insert, tt.value)
|
|
if tt.exception != "" {
|
|
require.Error(err)
|
|
assert.Containsf(err.Error(), tt.exception, "missing %s from err: %s", tt.exception, err.Error())
|
|
return
|
|
}
|
|
require.NoError(err)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestDomain_wt_name(t *testing.T) {
|
|
const (
|
|
createTable = `
|
|
create table if not exists test_table_wt_name (
|
|
id bigint generated always as identity primary key,
|
|
name wt_name
|
|
);
|
|
`
|
|
insert = `
|
|
insert into test_table_wt_name(name)
|
|
values ($1)
|
|
returning id;
|
|
`
|
|
)
|
|
|
|
conn, _ := TestSetup(t, "postgres")
|
|
db := conn.DB()
|
|
|
|
if _, err := db.Exec(createTable); err != nil {
|
|
t.Fatalf("query: \n%s\n error: %s", createTable, err)
|
|
}
|
|
|
|
failTests := []struct {
|
|
value string
|
|
exception string
|
|
}{
|
|
{" ", "wt_name_too_short"},
|
|
{"1" + strings.Repeat("1", 512), "wt_name_too_long"},
|
|
{"alice and bob's dinner", ""},
|
|
}
|
|
for _, tt := range failTests {
|
|
t.Run(tt.value, func(t *testing.T) {
|
|
assert, require := assert.New(t), require.New(t)
|
|
|
|
t.Logf("insert value: %q", tt.value)
|
|
_, err := db.Query(insert, tt.value)
|
|
if tt.exception != "" {
|
|
require.Error(err)
|
|
assert.Containsf(err.Error(), tt.exception, "missing %s from err: %s", tt.exception, err.Error())
|
|
return
|
|
}
|
|
require.NoError(err)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestDomain_wt_description(t *testing.T) {
|
|
const (
|
|
createTable = `
|
|
create table if not exists test_table_wt_description (
|
|
id bigint generated always as identity primary key,
|
|
description wt_description
|
|
);
|
|
`
|
|
insert = `
|
|
insert into test_table_wt_description(description)
|
|
values ($1)
|
|
returning id;
|
|
`
|
|
)
|
|
|
|
conn, _ := TestSetup(t, "postgres")
|
|
db := conn.DB()
|
|
|
|
if _, err := db.Exec(createTable); err != nil {
|
|
t.Fatalf("query: \n%s\n error: %s", createTable, err)
|
|
}
|
|
|
|
failTests := []struct {
|
|
value string
|
|
exception string
|
|
}{
|
|
{" ", "wt_description_too_short"},
|
|
{"1" + strings.Repeat("1", 1024), "wt_description_too_long"},
|
|
{"alice and bob's dinner has delicious pancakes", ""},
|
|
}
|
|
for _, tt := range failTests {
|
|
t.Run(tt.value, func(t *testing.T) {
|
|
assert, require := assert.New(t), require.New(t)
|
|
|
|
t.Logf("insert value: %q", tt.value)
|
|
_, err := db.Query(insert, tt.value)
|
|
if tt.exception != "" {
|
|
require.Error(err)
|
|
assert.Containsf(err.Error(), tt.exception, "missing %s from err: %s", tt.exception, err.Error())
|
|
return
|
|
}
|
|
require.NoError(err)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestDomain_wt_sentinel(t *testing.T) {
|
|
const (
|
|
createTable = `
|
|
create table if not exists test_table_wt_sentinel (
|
|
id bigint generated always as identity primary key,
|
|
sentinel wt_sentinel
|
|
);
|
|
`
|
|
insert = `
|
|
insert into test_table_wt_sentinel(sentinel)
|
|
values ($1)
|
|
returning id;
|
|
`
|
|
)
|
|
|
|
conn, _ := TestSetup(t, "postgres")
|
|
db := conn.DB()
|
|
|
|
if _, err := db.Exec(createTable); err != nil {
|
|
t.Fatalf("query: \n%s\n error: %s", createTable, err)
|
|
}
|
|
|
|
tests := []struct {
|
|
name string
|
|
value string
|
|
wantErr bool
|
|
}{
|
|
{"normal", "\ufffefoo", false},
|
|
{"normal non-sentinel", "foo", false},
|
|
{"trailing sentinel", "\ufffefoo\ufffe", false},
|
|
{"sentinel with space before word", "\ufffe foo", false},
|
|
{"sentinel with empty string", "\ufffe ", true},
|
|
{"multiple sentinels with empty string", "\ufffe\ufffe ", true},
|
|
{"multiple sentinels", "\ufffe\ufffefoo", false},
|
|
{"sentinel space sentinel space string", "\ufffe \ufffe foo ", false},
|
|
{"empty string", " ", true},
|
|
}
|
|
for _, tt := range tests {
|
|
tt := tt
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
assert := assert.New(t)
|
|
t.Logf("insert value: %q", tt.value)
|
|
_, err := db.Query(insert, tt.value)
|
|
if tt.wantErr {
|
|
assert.Error(err)
|
|
return
|
|
}
|
|
assert.NoError(err)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestDomain_wt_is_sentinel(t *testing.T) {
|
|
const (
|
|
query = `
|
|
select wt_is_sentinel($1);
|
|
`
|
|
)
|
|
|
|
conn, _ := TestSetup(t, "postgres")
|
|
db := conn.DB()
|
|
|
|
tests := []struct {
|
|
name string
|
|
value string
|
|
want bool
|
|
}{
|
|
{"normal", "\ufffefoo", true},
|
|
{"non-sentinel", "foo", false},
|
|
{"trailing sentinel", "\ufffefoo\ufffe", true},
|
|
{"sentinel with space before word", "\ufffe foo", true},
|
|
{"sentinel with empty string", "\ufffe ", false},
|
|
{"multiple sentinels with empty string", "\ufffe\ufffe ", false},
|
|
{"multiple sentinels", "\ufffe\ufffefoo", true},
|
|
{"sentinel space sentinel space string", "\ufffe \ufffe foo ", true},
|
|
{"empty string", " ", false},
|
|
}
|
|
for _, tt := range tests {
|
|
tt := tt
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
assert, require := assert.New(t), require.New(t)
|
|
t.Logf("query value: %q", tt.value)
|
|
|
|
var got bool
|
|
rows, err := db.Query(query, tt.value)
|
|
require.NoError(err)
|
|
defer rows.Close()
|
|
|
|
require.True(rows.Next())
|
|
require.NoError(rows.Scan(&got))
|
|
assert.Equal(tt.want, got)
|
|
|
|
require.False(rows.Next())
|
|
require.NoError(rows.Err())
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestDomain_wt_to_sentinel(t *testing.T) {
|
|
const (
|
|
query = `
|
|
select wt_to_sentinel($1);
|
|
`
|
|
)
|
|
|
|
conn, _ := TestSetup(t, "postgres")
|
|
db := conn.DB()
|
|
|
|
tests := []struct {
|
|
name string
|
|
value string
|
|
want string
|
|
}{
|
|
{"V", "foo", "\ufffefoo"},
|
|
{"space V", " foo", "\ufffefoo"},
|
|
{"V space", "foo ", "\ufffefoo"},
|
|
{"space space V", " foo ", "\ufffefoo"},
|
|
{"sentinel V", "\ufffefoo", "\ufffefoo"},
|
|
{"sentinel space V", "\ufffe foo", "\ufffefoo"},
|
|
{"sentinel V space", "\ufffefoo ", "\ufffefoo"},
|
|
{"sentinel sentinel V", "\ufffe\ufffefoo", "\ufffefoo"},
|
|
{"sentinel sentinel space V", "\ufffe\ufffe foo", "\ufffefoo"},
|
|
{"sentinel sentinel V space", "\ufffe\ufffefoo ", "\ufffefoo"},
|
|
{"sentinel space sentinel space V space", "\ufffe \ufffe foo ", "\ufffefoo"},
|
|
}
|
|
for _, tt := range tests {
|
|
tt := tt
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
assert, require := assert.New(t), require.New(t)
|
|
t.Logf("query value: %q", tt.value)
|
|
|
|
var got string
|
|
rows, err := db.Query(query, tt.value)
|
|
require.NoError(err)
|
|
defer rows.Close()
|
|
|
|
require.True(rows.Next())
|
|
require.NoError(rows.Scan(&got))
|
|
assert.Equal(tt.want, got)
|
|
|
|
require.False(rows.Next())
|
|
require.NoError(rows.Err())
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestDomain_not_null_columns_func(t *testing.T) {
|
|
const (
|
|
createTable = `
|
|
create table if not exists test_not_null_columns (
|
|
id bigint generated always as identity primary key,
|
|
one text,
|
|
two text,
|
|
three text
|
|
);
|
|
`
|
|
addTriggers = `
|
|
create trigger
|
|
test_insert_not_null_columns
|
|
before
|
|
insert on test_not_null_columns
|
|
for each row execute procedure not_null_columns('one', 'two');
|
|
`
|
|
dropTriggers = `drop trigger test_insert_not_null_columns on test_not_null_columns`
|
|
)
|
|
|
|
conn, _ := TestSetup(t, "postgres")
|
|
db := conn.DB()
|
|
_, err := db.Exec(createTable)
|
|
assert.NoError(t, err)
|
|
|
|
tests := []struct {
|
|
name string
|
|
stmt string
|
|
wantErr bool
|
|
}{
|
|
{
|
|
name: "null one",
|
|
stmt: "insert into test_not_null_columns (one, two, three) values (null, 'two', 'three');",
|
|
wantErr: true,
|
|
},
|
|
{
|
|
name: "null two",
|
|
stmt: "insert into test_not_null_columns (one, two, three) values ('one', null, 'three');",
|
|
wantErr: true,
|
|
},
|
|
{
|
|
name: "all null",
|
|
stmt: "insert into test_not_null_columns (one, two, three) values (null, null, null);",
|
|
wantErr: true,
|
|
},
|
|
{
|
|
name: "null three",
|
|
stmt: "insert into test_not_null_columns (one, two, three) values ('one', 'two', null);",
|
|
wantErr: false,
|
|
},
|
|
}
|
|
for _, tt := range tests {
|
|
tt := tt
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
require, assert := require.New(t), assert.New(t)
|
|
|
|
_, err = db.Exec(addTriggers)
|
|
require.NoError(err)
|
|
|
|
_, err = db.Exec(tt.stmt)
|
|
|
|
if !tt.wantErr {
|
|
assert.NoError(err)
|
|
return
|
|
}
|
|
require.Error(err)
|
|
|
|
// Drop trigger and run insert again, it should now insert successfully
|
|
_, err = db.Exec(dropTriggers)
|
|
assert.NoError(err)
|
|
|
|
_, err = db.Exec(tt.stmt)
|
|
assert.NoError(err)
|
|
})
|
|
}
|
|
}
|