phlo-postgres 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,15 @@
1
+ Metadata-Version: 2.4
2
+ Name: phlo-postgres
3
+ Version: 0.1.0
4
+ Summary: Postgres service plugin for Phlo
5
+ Author-email: Phlo Team <team@phlo.dev>
6
+ License: MIT
7
+ Requires-Python: >=3.11
8
+ Description-Content-Type: text/plain
9
+ Requires-Dist: phlo>=0.1.0
10
+ Requires-Dist: pyyaml>=6.0.1
11
+ Provides-Extra: dev
12
+ Requires-Dist: pytest>=7.0; extra == "dev"
13
+ Requires-Dist: ruff>=0.1.0; extra == "dev"
14
+
15
+ Postgres service plugin for Phlo.
@@ -0,0 +1,65 @@
1
+ # phlo-postgres
2
+
3
+ PostgreSQL database service for Phlo.
4
+
5
+ ## Description
6
+
7
+ Core PostgreSQL database for metadata storage, lineage tracking, and operational data. Includes optional Prometheus exporter for database metrics.
8
+
9
+ ## Installation
10
+
11
+ ```bash
12
+ pip install phlo-postgres
13
+ # or
14
+ phlo plugin install postgres
15
+ ```
16
+
17
+ ## Configuration
18
+
19
+ | Variable | Default | Description |
20
+ | ------------------------ | -------- | ---------------------- |
21
+ | `POSTGRES_PORT` | `5432` | PostgreSQL port |
22
+ | `POSTGRES_USER` | `phlo` | Database username |
23
+ | `POSTGRES_PASSWORD` | `phlo` | Database password |
24
+ | `POSTGRES_DB` | `phlo` | Database name |
25
+ | `POSTGRES_SSL_MODE` | `prefer` | SSL mode |
26
+ | `POSTGRES_EXPORTER_PORT` | `9187` | Postgres exporter port |
27
+
28
+ ## Auto-Configuration
29
+
30
+ This package is **fully auto-configured**:
31
+
32
+ | Feature | How It Works |
33
+ | ---------------------- | ---------------------------------------------------------- |
34
+ | **Grafana Datasource** | Auto-registers as Grafana datasource via labels |
35
+ | **postgres-exporter** | Optional Prometheus exporter for native PostgreSQL metrics |
36
+ | **Service Discovery** | Exporter auto-scraped by Prometheus |
37
+
38
+ ### Grafana Labels
39
+
40
+ ```yaml
41
+ compose:
42
+ labels:
43
+ phlo.grafana.datasource: "true"
44
+ phlo.grafana.datasource.type: "postgres"
45
+ phlo.grafana.datasource.name: "PostgreSQL"
46
+ ```
47
+
48
+ ## Usage
49
+
50
+ ```bash
51
+ # Start PostgreSQL
52
+ phlo services start --service postgres
53
+
54
+ # Start with exporter (for observability)
55
+ phlo services start --service postgres,postgres-exporter
56
+ ```
57
+
58
+ ## Endpoints
59
+
60
+ - **PostgreSQL**: `localhost:5432`
61
+ - **Exporter Metrics**: `http://localhost:9187/metrics`
62
+
63
+ ## Entry Points
64
+
65
+ - `phlo.plugins.services` - Provides `PostgresServicePlugin` and `PostgresExporterServicePlugin`
@@ -0,0 +1,42 @@
1
+ [build-system]
2
+ requires = ["setuptools>=45", "wheel"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "phlo-postgres"
7
+ version = "0.1.0"
8
+ description = "Postgres service plugin for Phlo"
9
+ readme = {text = "Postgres service plugin for Phlo.", content-type = "text/plain"}
10
+ requires-python = ">=3.11"
11
+ authors = [
12
+ {name = "Phlo Team", email = "team@phlo.dev"},
13
+ ]
14
+ license = {text = "MIT"}
15
+ dependencies = [
16
+ "phlo>=0.1.0",
17
+ "pyyaml>=6.0.1",
18
+ ]
19
+
20
+ [project.optional-dependencies]
21
+ dev = [
22
+ "pytest>=7.0",
23
+ "ruff>=0.1.0",
24
+ ]
25
+
26
+ [project.entry-points."phlo.plugins.services"]
27
+ postgres = "phlo_postgres.plugin:PostgresServicePlugin"
28
+ postgres-exporter = "phlo_postgres.plugin:PostgresExporterServicePlugin"
29
+
30
+ [tool.setuptools]
31
+ package-dir = {"" = "src"}
32
+ include-package-data = true
33
+
34
+ [tool.setuptools.packages.find]
35
+ where = ["src"]
36
+
37
+ [tool.setuptools.package-data]
38
+ phlo_postgres = ["service.yaml", "exporter_service.yaml"]
39
+
40
+ [tool.ruff]
41
+ line-length = 100
42
+ target-version = "py311"
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,7 @@
1
+ """Postgres service plugin package."""
2
+
3
+ from phlo_postgres.plugin import PostgresServicePlugin
4
+ from phlo_postgres.settings import PostgresSettings, get_settings
5
+
6
+ __all__ = ["PostgresServicePlugin", "PostgresSettings", "get_settings"]
7
+ __version__ = "0.1.0"
@@ -0,0 +1,39 @@
1
+ name: postgres-exporter
2
+ description: Prometheus exporter for PostgreSQL metrics
3
+ category: observability
4
+ default: false
5
+ profile: observability
6
+
7
+ image: quay.io/prometheuscommunity/postgres-exporter:v0.15.0
8
+
9
+ depends_on:
10
+ - postgres
11
+
12
+ compose:
13
+ restart: unless-stopped
14
+ labels:
15
+ phlo.metrics.enabled: "true"
16
+ phlo.metrics.port: "postgres-exporter:9187"
17
+ phlo.metrics.path: "/metrics"
18
+ environment:
19
+ DATA_SOURCE_NAME: postgresql://${POSTGRES_USER:-phlo}:${POSTGRES_PASSWORD:-phlo}@postgres:5432/${POSTGRES_DB:-phlo}?sslmode=disable
20
+ ports:
21
+ - "${POSTGRES_EXPORTER_PORT:-9187}:9187"
22
+ healthcheck:
23
+ test:
24
+ [
25
+ "CMD",
26
+ "wget",
27
+ "--quiet",
28
+ "--tries=1",
29
+ "--spider",
30
+ "http://localhost:9187/metrics",
31
+ ]
32
+ interval: 10s
33
+ timeout: 5s
34
+ retries: 5
35
+
36
+ env_vars:
37
+ POSTGRES_EXPORTER_PORT:
38
+ default: 9187
39
+ description: Postgres exporter metrics port
@@ -0,0 +1,47 @@
1
+ """Postgres service plugin."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from importlib import resources
6
+ from typing import Any
7
+
8
+ import yaml
9
+ from phlo.plugins import PluginMetadata, ServicePlugin
10
+
11
+
12
+ class PostgresServicePlugin(ServicePlugin):
13
+ """Service plugin for Postgres."""
14
+
15
+ @property
16
+ def metadata(self) -> PluginMetadata:
17
+ return PluginMetadata(
18
+ name="postgres",
19
+ version="0.1.0",
20
+ description="PostgreSQL database for metadata and operational storage",
21
+ author="Phlo Team",
22
+ tags=["core", "database", "postgres"],
23
+ )
24
+
25
+ @property
26
+ def service_definition(self) -> dict[str, Any]:
27
+ service_path = resources.files("phlo_postgres").joinpath("service.yaml")
28
+ return yaml.safe_load(service_path.read_text(encoding="utf-8"))
29
+
30
+
31
+ class PostgresExporterServicePlugin(ServicePlugin):
32
+ """Service plugin for Postgres Prometheus exporter."""
33
+
34
+ @property
35
+ def metadata(self) -> PluginMetadata:
36
+ return PluginMetadata(
37
+ name="postgres-exporter",
38
+ version="0.1.0",
39
+ description="Prometheus exporter for PostgreSQL metrics",
40
+ author="Phlo Team",
41
+ tags=["observability", "metrics", "postgres"],
42
+ )
43
+
44
+ @property
45
+ def service_definition(self) -> dict[str, Any]:
46
+ service_path = resources.files("phlo_postgres").joinpath("exporter_service.yaml")
47
+ return yaml.safe_load(service_path.read_text(encoding="utf-8"))
@@ -0,0 +1,60 @@
1
+ name: postgres
2
+ description: PostgreSQL database for metadata and operational storage
3
+ category: core
4
+ default: true
5
+
6
+ image: postgres:16-alpine
7
+
8
+ compose:
9
+ restart: unless-stopped
10
+ labels:
11
+ phlo.metrics.enabled: "false"
12
+ phlo.grafana.datasource: "true"
13
+ phlo.grafana.datasource.type: "postgres"
14
+ phlo.grafana.datasource.name: "PostgreSQL"
15
+ phlo.grafana.datasource.url: "postgres:5432"
16
+ phlo.grafana.datasource.database: "${POSTGRES_DB:-phlo}"
17
+ environment:
18
+ POSTGRES_USER: ${POSTGRES_USER:-phlo}
19
+ POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-phlo}
20
+ POSTGRES_DB: ${POSTGRES_DB:-phlo}
21
+ # SSL/TLS
22
+ POSTGRES_SSL_MODE: ${POSTGRES_SSL_MODE:-prefer}
23
+ ports:
24
+ - "${POSTGRES_PORT:-5432}:5432"
25
+ volumes:
26
+ - ./volumes/postgres:/var/lib/postgresql/data
27
+ - ./volumes/postgres-certs:/var/lib/postgresql/certs:ro
28
+ healthcheck:
29
+ test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER:-phlo}"]
30
+ interval: 10s
31
+ timeout: 5s
32
+ retries: 10
33
+
34
+ env_vars:
35
+ POSTGRES_USER:
36
+ default: phlo
37
+ description: PostgreSQL username
38
+ POSTGRES_PASSWORD:
39
+ default: phlo
40
+ description: PostgreSQL password
41
+ secret: true
42
+ POSTGRES_DB:
43
+ default: phlo
44
+ description: PostgreSQL database name
45
+ POSTGRES_PORT:
46
+ default: 5432
47
+ description: PostgreSQL host port
48
+ # SSL/TLS
49
+ POSTGRES_SSL_MODE:
50
+ default: "prefer"
51
+ description: "SSL mode: disable, allow, prefer, require, verify-ca, verify-full"
52
+ POSTGRES_SSL_CERT_FILE:
53
+ default: ""
54
+ description: "Path to SSL certificate file"
55
+ POSTGRES_SSL_KEY_FILE:
56
+ default: ""
57
+ description: "Path to SSL private key file"
58
+ POSTGRES_SSL_CA_FILE:
59
+ default: ""
60
+ description: "Path to SSL CA certificate file"
@@ -0,0 +1,34 @@
1
+ """Postgres settings."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from functools import lru_cache
6
+ from urllib.parse import quote_plus
7
+
8
+ from pydantic import Field
9
+
10
+ from phlo.config.base import BaseConfig
11
+
12
+
13
+ class PostgresSettings(BaseConfig):
14
+ """PostgreSQL database connection and schema configuration."""
15
+
16
+ postgres_host: str = Field(default="postgres", description="PostgreSQL host")
17
+ postgres_port: int = Field(default=10000, description="PostgreSQL port")
18
+ postgres_user: str = Field(default="lake", description="PostgreSQL username")
19
+ postgres_password: str = Field(default="phlo", description="PostgreSQL password")
20
+ postgres_db: str = Field(default="lakehouse", description="PostgreSQL database name")
21
+ postgres_mart_schema: str = Field(
22
+ default="marts", description="Schema for published mart tables"
23
+ )
24
+
25
+ def get_postgres_connection_string(self, include_db: bool = True) -> str:
26
+ db_part = f"/{self.postgres_db}" if include_db else ""
27
+ user = quote_plus(self.postgres_user)
28
+ password = quote_plus(self.postgres_password)
29
+ return f"postgresql://{user}:{password}@{self.postgres_host}:{self.postgres_port}{db_part}"
30
+
31
+
32
+ @lru_cache(maxsize=1)
33
+ def get_settings() -> PostgresSettings:
34
+ return PostgresSettings()
@@ -0,0 +1,15 @@
1
+ Metadata-Version: 2.4
2
+ Name: phlo-postgres
3
+ Version: 0.1.0
4
+ Summary: Postgres service plugin for Phlo
5
+ Author-email: Phlo Team <team@phlo.dev>
6
+ License: MIT
7
+ Requires-Python: >=3.11
8
+ Description-Content-Type: text/plain
9
+ Requires-Dist: phlo>=0.1.0
10
+ Requires-Dist: pyyaml>=6.0.1
11
+ Provides-Extra: dev
12
+ Requires-Dist: pytest>=7.0; extra == "dev"
13
+ Requires-Dist: ruff>=0.1.0; extra == "dev"
14
+
15
+ Postgres service plugin for Phlo.
@@ -0,0 +1,15 @@
1
+ README.md
2
+ pyproject.toml
3
+ src/phlo_postgres/__init__.py
4
+ src/phlo_postgres/exporter_service.yaml
5
+ src/phlo_postgres/plugin.py
6
+ src/phlo_postgres/service.yaml
7
+ src/phlo_postgres/settings.py
8
+ src/phlo_postgres.egg-info/PKG-INFO
9
+ src/phlo_postgres.egg-info/SOURCES.txt
10
+ src/phlo_postgres.egg-info/dependency_links.txt
11
+ src/phlo_postgres.egg-info/entry_points.txt
12
+ src/phlo_postgres.egg-info/requires.txt
13
+ src/phlo_postgres.egg-info/top_level.txt
14
+ tests/test_integration_postgres.py
15
+ tests/test_postgres_plugin.py
@@ -0,0 +1,3 @@
1
+ [phlo.plugins.services]
2
+ postgres = phlo_postgres.plugin:PostgresServicePlugin
3
+ postgres-exporter = phlo_postgres.plugin:PostgresExporterServicePlugin
@@ -0,0 +1,6 @@
1
+ phlo>=0.1.0
2
+ pyyaml>=6.0.1
3
+
4
+ [dev]
5
+ pytest>=7.0
6
+ ruff>=0.1.0
@@ -0,0 +1 @@
1
+ phlo_postgres
@@ -0,0 +1,295 @@
1
+ """Comprehensive integration tests for phlo-postgres.
2
+
3
+ Per TEST_STRATEGY.md Level 2 (Functional):
4
+ - Connection String Building: Test connection string construction
5
+ - DB Ops: Create user, create DB, verify connectivity
6
+ - Service Plugin: Verify plugin registration
7
+ """
8
+
9
+ from unittest.mock import patch, MagicMock
10
+
11
+ import pytest
12
+
13
+ pytestmark = pytest.mark.integration
14
+
15
+
16
+ # =============================================================================
17
+ # Connection Configuration Tests
18
+ # =============================================================================
19
+
20
+
21
+ class TestPostgresConfiguration:
22
+ """Test Postgres configuration and connection building."""
23
+
24
+ def test_postgres_config_accessible(self):
25
+ """Test Postgres configuration is accessible from phlo_postgres.settings."""
26
+ from phlo_postgres.settings import get_settings
27
+
28
+ settings = get_settings()
29
+
30
+ assert hasattr(settings, "postgres_host")
31
+ assert hasattr(settings, "postgres_port")
32
+ assert hasattr(settings, "postgres_db")
33
+ assert hasattr(settings, "postgres_user")
34
+
35
+ def test_postgres_config_has_defaults(self):
36
+ """Test Postgres config has sensible defaults."""
37
+ from phlo_postgres.settings import get_settings
38
+
39
+ settings = get_settings()
40
+
41
+ assert settings.postgres_host is not None
42
+ assert settings.postgres_port > 0
43
+ assert settings.postgres_db is not None
44
+
45
+ def test_connection_string_building(self):
46
+ """Test building a connection string from config."""
47
+ from phlo_postgres.settings import get_settings
48
+
49
+ settings = get_settings()
50
+
51
+ # Build connection string
52
+ conn_str = (
53
+ f"postgresql://{settings.postgres_user}:{settings.postgres_password}"
54
+ f"@{settings.postgres_host}:{settings.postgres_port}/{settings.postgres_db}"
55
+ )
56
+
57
+ assert "postgresql://" in conn_str
58
+ assert str(settings.postgres_port) in conn_str
59
+
60
+
61
+ # =============================================================================
62
+ # Service Plugin Tests
63
+ # =============================================================================
64
+
65
+
66
+ class TestPostgresServicePlugin:
67
+ """Test Postgres service plugin."""
68
+
69
+ def test_plugin_initializes(self):
70
+ """Test PostgresServicePlugin can be instantiated."""
71
+ from phlo_postgres.plugin import PostgresServicePlugin
72
+
73
+ plugin = PostgresServicePlugin()
74
+ assert plugin is not None
75
+
76
+ def test_plugin_metadata(self):
77
+ """Test plugin metadata is correctly defined."""
78
+ from phlo_postgres.plugin import PostgresServicePlugin
79
+
80
+ plugin = PostgresServicePlugin()
81
+ metadata = plugin.metadata
82
+
83
+ assert metadata.name == "postgres"
84
+ assert metadata.version is not None
85
+
86
+ def test_service_definition_loads(self):
87
+ """Test service definition YAML can be loaded."""
88
+ from phlo_postgres.plugin import PostgresServicePlugin
89
+
90
+ plugin = PostgresServicePlugin()
91
+ service_def = plugin.service_definition
92
+
93
+ assert isinstance(service_def, dict)
94
+ # Service definitions have flat structure with 'name' and 'compose' keys
95
+ assert "name" in service_def or "compose" in service_def
96
+
97
+ def test_service_definition_has_image(self):
98
+ """Test service definition has container image specified."""
99
+ from phlo_postgres.plugin import PostgresServicePlugin
100
+
101
+ plugin = PostgresServicePlugin()
102
+ service_def = plugin.service_definition
103
+
104
+ # Navigate to find image
105
+ services = service_def.get("services", {})
106
+ if services:
107
+ # Should have postgres service with image
108
+ for svc_name, svc_config in services.items():
109
+ if "postgres" in svc_name.lower():
110
+ assert "image" in svc_config or "build" in svc_config
111
+
112
+
113
+ # =============================================================================
114
+ # Psycopg2 Connection Tests (with mocks)
115
+ # =============================================================================
116
+
117
+
118
+ class TestPostgresConnectionMocked:
119
+ """Test Postgres connections with mocks."""
120
+
121
+ def test_psycopg2_connect_with_config(self):
122
+ """Test psycopg2 connection using config values."""
123
+ import psycopg2
124
+ from phlo_postgres.settings import get_settings
125
+
126
+ settings = get_settings()
127
+
128
+ mock_conn = MagicMock()
129
+
130
+ with patch("psycopg2.connect", return_value=mock_conn) as mock_connect:
131
+ # Simulate what most code does
132
+ psycopg2.connect(
133
+ host=settings.postgres_host,
134
+ port=settings.postgres_port,
135
+ database=settings.postgres_db,
136
+ user=settings.postgres_user,
137
+ password=settings.postgres_password,
138
+ )
139
+
140
+ mock_connect.assert_called_once()
141
+ # Verify kwargs
142
+ call_kwargs = mock_connect.call_args.kwargs
143
+ assert call_kwargs["host"] == settings.postgres_host
144
+ assert call_kwargs["database"] == settings.postgres_db
145
+
146
+ def test_connection_with_cursor_context(self):
147
+ """Test connection with cursor context manager."""
148
+ mock_cursor = MagicMock()
149
+ mock_cursor.fetchall.return_value = [(1,)]
150
+
151
+ mock_conn = MagicMock()
152
+ mock_conn.cursor.return_value.__enter__ = lambda _: mock_cursor
153
+ mock_conn.cursor.return_value.__exit__ = MagicMock(return_value=False)
154
+
155
+ with patch("psycopg2.connect", return_value=mock_conn):
156
+ import psycopg2
157
+
158
+ conn = psycopg2.connect(host="localhost", database="test")
159
+ with conn.cursor() as cur:
160
+ cur.execute("SELECT 1")
161
+ result = cur.fetchall()
162
+
163
+ assert result == [(1,)]
164
+
165
+
166
+ # =============================================================================
167
+ # Functional Integration Tests (Real Postgres if available)
168
+ # =============================================================================
169
+
170
+
171
+ @pytest.fixture
172
+ def postgres_connection():
173
+ """Fixture providing a real Postgres connection if available."""
174
+ from phlo_postgres.settings import get_settings
175
+ import psycopg2
176
+
177
+ settings = get_settings()
178
+
179
+ try:
180
+ conn = psycopg2.connect(
181
+ host=settings.postgres_host,
182
+ port=settings.postgres_port,
183
+ database=settings.postgres_db,
184
+ user=settings.postgres_user,
185
+ password=settings.postgres_password,
186
+ connect_timeout=5,
187
+ )
188
+ yield conn
189
+ conn.close()
190
+ except Exception as e:
191
+ pytest.skip(f"Postgres not available: {e}")
192
+
193
+
194
+ class TestPostgresIntegrationReal:
195
+ """Real integration tests against a running Postgres instance."""
196
+
197
+ def test_simple_query(self, postgres_connection):
198
+ """Test simple query execution against real Postgres."""
199
+ with postgres_connection.cursor() as cur:
200
+ cur.execute("SELECT 1 AS one")
201
+ result = cur.fetchall()
202
+
203
+ assert result == [(1,)]
204
+
205
+ def test_version_query(self, postgres_connection):
206
+ """Test querying Postgres version."""
207
+ with postgres_connection.cursor() as cur:
208
+ cur.execute("SELECT version()")
209
+ result = cur.fetchone()
210
+
211
+ assert result is not None
212
+ assert "PostgreSQL" in result[0]
213
+
214
+ def test_create_temp_table(self, postgres_connection):
215
+ """Test creating and querying a temporary table."""
216
+ with postgres_connection.cursor() as cur:
217
+ cur.execute("""
218
+ CREATE TEMP TABLE test_table (
219
+ id SERIAL PRIMARY KEY,
220
+ name VARCHAR(100)
221
+ )
222
+ """)
223
+ cur.execute("INSERT INTO test_table (name) VALUES ('test1'), ('test2')")
224
+ cur.execute("SELECT COUNT(*) FROM test_table")
225
+ result = cur.fetchone()
226
+
227
+ assert result[0] == 2
228
+
229
+ def test_list_databases(self, postgres_connection):
230
+ """Test listing databases."""
231
+ with postgres_connection.cursor() as cur:
232
+ cur.execute("""
233
+ SELECT datname FROM pg_database
234
+ WHERE datistemplate = false
235
+ """)
236
+ databases = [row[0] for row in cur.fetchall()]
237
+
238
+ # Should have at least the connected database
239
+ assert len(databases) >= 1
240
+
241
+ def test_list_tables(self, postgres_connection):
242
+ """Test listing tables in public schema."""
243
+ with postgres_connection.cursor() as cur:
244
+ cur.execute("""
245
+ SELECT table_name
246
+ FROM information_schema.tables
247
+ WHERE table_schema = 'public'
248
+ """)
249
+ tables = [row[0] for row in cur.fetchall()]
250
+
251
+ # May be empty if no tables
252
+ assert isinstance(tables, list)
253
+
254
+ def test_transaction_rollback(self, postgres_connection):
255
+ """Test transaction rollback."""
256
+ postgres_connection.autocommit = False
257
+
258
+ with postgres_connection.cursor() as cur:
259
+ cur.execute("CREATE TEMP TABLE rollback_test (id INT)")
260
+ cur.execute("INSERT INTO rollback_test VALUES (1)")
261
+
262
+ # Rollback
263
+ postgres_connection.rollback()
264
+
265
+ # Table should not exist after rollback
266
+ with postgres_connection.cursor() as cur:
267
+ cur.execute("""
268
+ SELECT COUNT(*) FROM information_schema.tables
269
+ WHERE table_name = 'rollback_test'
270
+ """)
271
+ result = cur.fetchone()
272
+
273
+ # This may vary based on temp table behavior
274
+ assert result is not None
275
+
276
+
277
+ # =============================================================================
278
+ # Version and Export Tests
279
+ # =============================================================================
280
+
281
+
282
+ class TestPostgresExports:
283
+ """Test module exports and version."""
284
+
285
+ def test_plugin_importable(self):
286
+ """Test PostgresServicePlugin is importable."""
287
+ from phlo_postgres.plugin import PostgresServicePlugin
288
+
289
+ assert PostgresServicePlugin is not None
290
+
291
+ def test_module_importable(self):
292
+ """Test phlo_postgres module is importable."""
293
+ import phlo_postgres
294
+
295
+ assert phlo_postgres is not None
@@ -0,0 +1,11 @@
1
+ """Tests for Postgres service plugin."""
2
+
3
+ from phlo_postgres.plugin import PostgresServicePlugin
4
+
5
+
6
+ def test_postgres_service_definition():
7
+ plugin = PostgresServicePlugin()
8
+ service_definition = plugin.service_definition
9
+
10
+ assert service_definition["name"] == "postgres"
11
+ assert service_definition["category"] == "core"