summaryrefslogtreecommitdiff
path: root/src/interfaces/libpq/t/006_service.pl
blob: 797e6232b8fcbc226c3a9ca769339b986f94ca25 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
# Copyright (c) 2025, PostgreSQL Global Development Group
use strict;
use warnings FATAL => 'all';
use File::Copy;
use PostgreSQL::Test::Utils;
use PostgreSQL::Test::Cluster;
use Test::More;

# This tests scenarios related to the service name and the service file,
# for the connection options and their environment variables.

my $node = PostgreSQL::Test::Cluster->new('node');
$node->init;
$node->start;

# Set up a dummy node used for the connection tests, but do not start it.
# This ensures that the environment variables used for the connection do
# not interfere with the connection attempts, and that the service file's
# contents are used.
my $dummy_node = PostgreSQL::Test::Cluster->new('dummy_node');
$dummy_node->init;

my $td = PostgreSQL::Test::Utils::tempdir;

# Windows vs non-Windows: CRLF vs LF for the file's newline, relying on
# the fact that libpq uses fgets() when reading the lines of a service file.
my $newline = $windows_os ? "\r\n" : "\n";

# Create the set of service files used in the tests.
# File that includes a valid service name, and uses a decomposed connection
# string for its contents, split on spaces.
my $srvfile_valid = "$td/pg_service_valid.conf";
append_to_file($srvfile_valid, "[my_srv]" . $newline);
foreach my $param (split(/\s+/, $node->connstr))
{
	append_to_file($srvfile_valid, $param . $newline);
}

# File defined with no contents, used as default value for PGSERVICEFILE,
# so as no lookup is attempted in the user's home directory.
my $srvfile_empty = "$td/pg_service_empty.conf";
append_to_file($srvfile_empty, '');

# Default service file in PGSYSCONFDIR.
my $srvfile_default = "$td/pg_service.conf";

# Missing service file.
my $srvfile_missing = "$td/pg_service_missing.conf";

# Service file with nested "service" defined.
my $srvfile_nested = "$td/pg_service_nested.conf";
copy($srvfile_valid, $srvfile_nested)
  or die "Could not copy $srvfile_valid to $srvfile_nested: $!";
append_to_file($srvfile_nested, 'service=invalid_srv' . $newline);

# Service file with nested "servicefile" defined.
my $srvfile_nested_2 = "$td/pg_service_nested_2.conf";
copy($srvfile_valid, $srvfile_nested_2)
  or die "Could not copy $srvfile_valid to $srvfile_nested_2: $!";
append_to_file($srvfile_nested_2,
	'servicefile=' . $srvfile_default . $newline);

# Set the fallback directory lookup of the service file to the temporary
# directory of this test.  PGSYSCONFDIR is used if the service file
# defined in PGSERVICEFILE cannot be found, or when a service file is
# found but not the service name.
local $ENV{PGSYSCONFDIR} = $td;
# Force PGSERVICEFILE to a default location, so as this test never
# tries to look at a home directory.  This value needs to remain
# at the top of this script before running any tests, and should never
# be changed.
local $ENV{PGSERVICEFILE} = "$srvfile_empty";

# Checks combinations of service name and a valid service file.
{
	local $ENV{PGSERVICEFILE} = $srvfile_valid;
	$dummy_node->connect_ok(
		'service=my_srv',
		'connection with correct "service" string and PGSERVICEFILE',
		sql => "SELECT 'connect1_1'",
		expected_stdout => qr/connect1_1/);

	$dummy_node->connect_ok(
		'postgres://?service=my_srv',
		'connection with correct "service" URI and PGSERVICEFILE',
		sql => "SELECT 'connect1_2'",
		expected_stdout => qr/connect1_2/);

	$dummy_node->connect_fails(
		'service=undefined-service',
		'connection with incorrect "service" string and PGSERVICEFILE',
		expected_stderr =>
		  qr/definition of service "undefined-service" not found/);

	local $ENV{PGSERVICE} = 'my_srv';
	$dummy_node->connect_ok(
		'',
		'connection with correct PGSERVICE and PGSERVICEFILE',
		sql => "SELECT 'connect1_3'",
		expected_stdout => qr/connect1_3/);

	local $ENV{PGSERVICE} = 'undefined-service';
	$dummy_node->connect_fails(
		'',
		'connection with incorrect PGSERVICE and PGSERVICEFILE',
		expected_stdout =>
		  qr/definition of service "undefined-service" not found/);
}

# Checks case of incorrect service file.
{
	local $ENV{PGSERVICEFILE} = $srvfile_missing;
	$dummy_node->connect_fails(
		'service=my_srv',
		'connection with correct "service" string and incorrect PGSERVICEFILE',
		expected_stderr =>
		  qr/service file ".*pg_service_missing.conf" not found/);
}

# Checks case of service file named "pg_service.conf" in PGSYSCONFDIR.
{
	# Create copy of valid file
	my $srvfile_default = "$td/pg_service.conf";
	copy($srvfile_valid, $srvfile_default);

	$dummy_node->connect_ok(
		'service=my_srv',
		'connection with correct "service" string and pg_service.conf',
		sql => "SELECT 'connect2_1'",
		expected_stdout => qr/connect2_1/);

	$dummy_node->connect_ok(
		'postgres://?service=my_srv',
		'connection with correct "service" URI and default pg_service.conf',
		sql => "SELECT 'connect2_2'",
		expected_stdout => qr/connect2_2/);

	$dummy_node->connect_fails(
		'service=undefined-service',
		'connection with incorrect "service" string and default pg_service.conf',
		expected_stderr =>
		  qr/definition of service "undefined-service" not found/);

	local $ENV{PGSERVICE} = 'my_srv';
	$dummy_node->connect_ok(
		'',
		'connection with correct PGSERVICE and default pg_service.conf',
		sql => "SELECT 'connect2_3'",
		expected_stdout => qr/connect2_3/);

	local $ENV{PGSERVICE} = 'undefined-service';
	$dummy_node->connect_fails(
		'',
		'connection with incorrect PGSERVICE and default pg_service.conf',
		expected_stdout =>
		  qr/definition of service "undefined-service" not found/);

	# Remove default pg_service.conf.
	unlink($srvfile_default);
}

# Checks nested service file contents.
{
	local $ENV{PGSERVICEFILE} = $srvfile_nested;

	$dummy_node->connect_fails(
		'service=my_srv',
		'connection with "service" in nested service file',
		expected_stderr =>
		  qr/nested "service" specifications not supported in service file/);

	local $ENV{PGSERVICEFILE} = $srvfile_nested_2;

	$dummy_node->connect_fails(
		'service=my_srv',
		'connection with "servicefile" in nested service file',
		expected_stderr =>
		  qr/nested "servicefile" specifications not supported in service file/
	);
}

# Properly escape backslashes in the path, to ensure the generation of
# correct connection strings.
my $srvfile_win_cared = $srvfile_valid;
$srvfile_win_cared =~ s/\\/\\\\/g;

# Checks that the "servicefile" option works as expected
{
	$dummy_node->connect_ok(
		q{service=my_srv servicefile='} . $srvfile_win_cared . q{'},
		'connection with valid servicefile in connection string',
		sql => "SELECT 'connect3_1'",
		expected_stdout => qr/connect3_1/);

	# Encode slashes and backslash
	my $encoded_srvfile = $srvfile_valid =~ s{([\\/])}{
		$1 eq '/' ? '%2F' : '%5C'
	}ger;

	# Additionally encode a colon in servicefile path of Windows
	$encoded_srvfile =~ s/:/%3A/g;

	$dummy_node->connect_ok(
		'postgresql:///?service=my_srv&servicefile=' . $encoded_srvfile,
		'connection with valid servicefile in URI',
		sql => "SELECT 'connect3_2'",
		expected_stdout => qr/connect3_2/);

	local $ENV{PGSERVICE} = 'my_srv';
	$dummy_node->connect_ok(
		q{servicefile='} . $srvfile_win_cared . q{'},
		'connection with PGSERVICE and servicefile in connection string',
		sql => "SELECT 'connect3_3'",
		expected_stdout => qr/connect3_3/);

	$dummy_node->connect_ok(
		'postgresql://?servicefile=' . $encoded_srvfile,
		'connection with PGSERVICE and servicefile in URI',
		sql => "SELECT 'connect3_4'",
		expected_stdout => qr/connect3_4/);
}

# Check that the "servicefile" option takes priority over the PGSERVICEFILE
# environment variable.
{
	local $ENV{PGSERVICEFILE} = 'non-existent-file.conf';

	$dummy_node->connect_fails(
		'service=my_srv',
		'connection with invalid PGSERVICEFILE',
		expected_stderr =>
		  qr/service file "non-existent-file\.conf" not found/);

	$dummy_node->connect_ok(
		q{service=my_srv servicefile='} . $srvfile_win_cared . q{'},
		'connection with both servicefile and PGSERVICEFILE',
		sql => "SELECT 'connect4_1'",
		expected_stdout => qr/connect4_1/);
}

$node->teardown_node;

done_testing();