Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 0 additions & 11 deletions cli/bin/postgres-ai.ts
Original file line number Diff line number Diff line change
Expand Up @@ -195,10 +195,6 @@ function createTtySpinner(
};
}

// ============================================================================
// Checkup command helpers
// ============================================================================

interface CheckupOptions {
checkId: string;
nodeName: string;
Expand Down Expand Up @@ -409,13 +405,6 @@ function printUploadSummary(
}
}

// ============================================================================
// CLI configuration
// ============================================================================

/**
* CLI configuration options
*/
interface CliOptions {
apiKey?: string;
apiBaseUrl?: string;
Expand Down
8 changes: 0 additions & 8 deletions cli/lib/checkup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -955,10 +955,6 @@ function resolveBuildTs(): string | null {
}
}

// ============================================================================
// Unified Report Generator Helpers
// ============================================================================

/**
* Generate a simple version report (A002, A013).
* These reports only contain PostgreSQL version information.
Expand Down Expand Up @@ -1034,10 +1030,6 @@ async function generateIndexReport<T extends { index_size_bytes: number }>(
return report;
}

// ============================================================================
// Report Generators (using unified helpers)
// ============================================================================

/** Generate A002 report - Postgres major version */
export const generateA002 = (client: Client, nodeName = "node-01") =>
generateVersionReport(client, nodeName, "A002", "Postgres major version");
Expand Down
4 changes: 0 additions & 4 deletions cli/lib/issues.ts
Original file line number Diff line number Diff line change
Expand Up @@ -670,10 +670,6 @@ export async function updateIssueComment(params: UpdateIssueCommentParams): Prom
}
}

// ============================================================================
// Action Items API Functions
// ============================================================================

export interface FetchActionItemParams {
apiKey: string;
apiBaseUrl: string;
Expand Down
21 changes: 1 addition & 20 deletions cli/lib/reports.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,5 @@
import { formatHttpError, maskSecret, normalizeBaseUrl } from "./util";

// ============================================================================
// Types
// ============================================================================

export interface CheckupReport {
id: number;
org_id: number;
Expand Down Expand Up @@ -32,10 +28,6 @@ export interface CheckupReportFileData extends CheckupReportFile {
data: string;
}

// ============================================================================
// Date parsing
// ============================================================================

/**
* Parse a date string in various formats into an ISO 8601 string.
* Supported formats:
Expand Down Expand Up @@ -73,10 +65,6 @@ export function parseFlexibleDate(input: string): string {
throw new Error(`Unrecognized date format: ${input}. Use YYYY-MM-DD or DD.MM.YYYY`);
}

// ============================================================================
// Params
// ============================================================================

export interface FetchReportsParams {
apiKey: string;
apiBaseUrl: string;
Expand Down Expand Up @@ -107,10 +95,6 @@ export interface FetchReportFileDataParams {
debug?: boolean;
}

// ============================================================================
// API functions
// ============================================================================

export async function fetchReports(params: FetchReportsParams): Promise<CheckupReport[]> {
const { apiKey, apiBaseUrl, projectId, status, limit = 20, beforeDate, beforeId, debug } = params;
if (!apiKey) {
Expand Down Expand Up @@ -299,10 +283,7 @@ export async function fetchReportFileData(params: FetchReportFileDataParams): Pr
}
}

// ============================================================================
// Lightweight markdown terminal renderer
// ============================================================================

/** Lightweight markdown terminal renderer. */
export function renderMarkdownForTerminal(md: string): string {
if (!md) return "";

Expand Down
8 changes: 2 additions & 6 deletions cli/test/init.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1318,12 +1318,8 @@ describe.skipIf(!dockerAvailable)("imageTag priority behavior", () => {
}, 60000);
});

// ---------------------------------------------------------------------------
// connectWithSslFallback — connectionTimeoutMillis and statement_timeout
// Issues 9 & 10
// ---------------------------------------------------------------------------
describe("connectWithSslFallback", () => {
// Issue 9: Verify that connectionTimeoutMillis is forwarded to the pg Client
// Verify that connectionTimeoutMillis is forwarded to the pg Client
// constructor so slow-responding servers don't hang the CLI indefinitely.
// Direct integration testing against a real TCP timeout would be flaky in CI,
// so we use a mock ClientClass and assert the config passed to its constructor.
Expand All @@ -1348,7 +1344,7 @@ describe("connectWithSslFallback", () => {
expect(receivedConfigs[0].connectionTimeoutMillis).toBe(10_000);
});

// Issue 10: Verify that SET statement_timeout is issued after every successful
// Verify that SET statement_timeout is issued after every successful
// connection to prevent runaway queries from blocking the CLI.
test("issues SET statement_timeout = '30s' after connecting", async () => {
const queriesSent: string[] = [];
Expand Down
9 changes: 0 additions & 9 deletions cli/test/reports.cli.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -167,9 +167,6 @@ async function startFakeApi() {
};
}

// ---------------------------------------------------------------------------
// Help output
// ---------------------------------------------------------------------------
describe("CLI reports command group", () => {
test("reports help exposes list, files, data subcommands", () => {
const r = runCli(["reports", "--help"], isolatedEnv());
Expand All @@ -180,9 +177,6 @@ describe("CLI reports command group", () => {
expect(out).toContain("data");
});

// -----------------------------------------------------------------------
// Input validation
// -----------------------------------------------------------------------
test("reports list fails fast when API key is missing", () => {
const r = runCli(["reports", "list"], isolatedEnv());
expect(r.status).toBe(1);
Expand Down Expand Up @@ -237,9 +231,6 @@ describe("CLI reports command group", () => {
expect(`${r.stdout}\n${r.stderr}`).toContain("Either reportId or --check-id is required");
});

// -----------------------------------------------------------------------
// Successful API calls
// -----------------------------------------------------------------------
test("reports list succeeds against a fake API", async () => {
const api = await startFakeApi();
try {
Expand Down
18 changes: 0 additions & 18 deletions cli/test/reports.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,6 @@ import {

const originalFetch = globalThis.fetch;

// ---------------------------------------------------------------------------
// fetchReports
// ---------------------------------------------------------------------------
describe("fetchReports", () => {
afterEach(() => {
globalThis.fetch = originalFetch;
Expand Down Expand Up @@ -274,9 +271,6 @@ describe("fetchReports", () => {
});
});

// ---------------------------------------------------------------------------
// fetchAllReports
// ---------------------------------------------------------------------------
describe("fetchAllReports", () => {
afterEach(() => {
globalThis.fetch = originalFetch;
Expand Down Expand Up @@ -416,9 +410,6 @@ describe("fetchAllReports", () => {
});
});

// ---------------------------------------------------------------------------
// fetchReportFiles
// ---------------------------------------------------------------------------
describe("fetchReportFiles", () => {
afterEach(() => {
globalThis.fetch = originalFetch;
Expand Down Expand Up @@ -623,9 +614,6 @@ describe("fetchReportFiles", () => {
});
});

// ---------------------------------------------------------------------------
// fetchReportFileData
// ---------------------------------------------------------------------------
describe("fetchReportFileData", () => {
afterEach(() => {
globalThis.fetch = originalFetch;
Expand Down Expand Up @@ -841,9 +829,6 @@ describe("fetchReportFileData", () => {
});
});

// ---------------------------------------------------------------------------
// renderMarkdownForTerminal
// ---------------------------------------------------------------------------
describe("renderMarkdownForTerminal", () => {
test("returns empty string for empty input", () => {
expect(renderMarkdownForTerminal("")).toBe("");
Expand Down Expand Up @@ -923,9 +908,6 @@ describe("renderMarkdownForTerminal", () => {
});
});

// ---------------------------------------------------------------------------
// parseFlexibleDate
// ---------------------------------------------------------------------------
describe("parseFlexibleDate", () => {
test("parses YYYY-MM-DD", () => {
expect(parseFlexibleDate("2025-01-15")).toBe("2025-01-15T00:00:00.000Z");
Expand Down
14 changes: 5 additions & 9 deletions monitoring_flask_backend/test_app.py
Original file line number Diff line number Diff line change
Expand Up @@ -1877,12 +1877,8 @@ def test_statement_timeout_returns_empty_metrics(self, mock_connect, client):
assert 'pgwatch_query_info{' not in data


# ---------------------------------------------------------------------------
# Additional coverage for /execute-query (issues 4, 6, 7, 8)
# ---------------------------------------------------------------------------

class TestExecuteQuerySuccessPath:
"""Issue 4 — success-path test: valid SELECT returning real results."""
"""Success-path test: valid SELECT returning real results."""

def test_returns_columns_and_rows(self, client, debug_mode):
"""A valid SELECT query returns 200 with populated columns and rows."""
Expand All @@ -1905,7 +1901,7 @@ def test_returns_columns_and_rows(self, client, debug_mode):


class TestExecuteQueryAuthSchemes:
"""Issue 6 — non-Bearer auth schemes (Basic, Token) must be rejected."""
"""Non-Bearer auth schemes (Basic, Token) must be rejected."""

def test_basic_auth_rejected(self, client, debug_mode):
"""Authorization: Basic <token> is not accepted (only Bearer is valid)."""
Expand Down Expand Up @@ -1941,7 +1937,7 @@ def test_apikey_scheme_rejected(self, client, debug_mode):


class TestExecuteQueryMultipleBlockComments:
"""Issue 7 — multiple sequential leading block comments before SELECT."""
"""Multiple sequential leading block comments before SELECT."""

def test_two_sequential_block_comments_accepted(self, client, debug_mode):
"""/* a */ /* b */ SELECT 1 passes the allowlist (both comments stripped)."""
Expand Down Expand Up @@ -1979,7 +1975,7 @@ def test_three_sequential_block_comments_accepted(self, client, debug_mode):


class TestExecuteQueryStatementTimeout:
"""Issue 8 — statement timeout during query execution returns 500."""
"""Statement timeout during query execution returns 500."""

def test_statement_timeout_returns_500(self, client, debug_mode):
"""OperationalError from statement_timeout inside execute-query returns 500."""
Expand All @@ -2001,7 +1997,7 @@ def test_statement_timeout_returns_500(self, client, debug_mode):
assert 'error' in data

def test_line_comment_containing_blocked_keyword_not_blocked(self, client, debug_mode):
"""Issue 2 regression: '-- pg_read_file' in a line comment must not trigger blocklist."""
"""Regression: '-- pg_read_file' in a line comment must not trigger blocklist."""
with patch('app.psycopg2.connect') as mock_connect:
mock_cursor = MagicMock()
mock_cursor.fetchall.return_value = [(1,)]
Expand Down
1 change: 0 additions & 1 deletion quality/scripts/release-readiness.sh
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
# shellcheck shell=bash
#
# Release Readiness Check
# TODO: add bats tests for this script
#
# Verifies that the codebase is ready for release by checking:
# 1. Git status (clean working tree, release-eligible branch)
Expand Down
31 changes: 7 additions & 24 deletions reporter/postgres_reports.py
Original file line number Diff line number Diff line change
Expand Up @@ -346,24 +346,20 @@ def get_queryid_queries_from_sink(self, query_text_limit: int = 655360, db_names
"""
cursor.execute(query)

# Use iterator to fetch rows in batches instead of loading all at once
for row in cursor:
db_name = row['dbname']
queryid = row['queryid']
query_text = row['query']

# Skip if queryid is missing

if not queryid:
continue

# Truncate query text if it exceeds the limit

if query_text and len(query_text) > query_text_limit:
query_text = query_text[:query_text_limit] + '...'

# Initialize database dict if needed

if db_name not in queries_by_db:
queries_by_db[db_name] = {}

queries_by_db[db_name][queryid] = query_text or ''

except Exception as e:
Expand Down Expand Up @@ -639,22 +635,9 @@ def generate_a007_altered_settings_report(self, cluster: str = "local", node_nam

return self.format_report_data("A007", altered_settings, node_name, postgres_version=self._get_postgres_version_info(cluster, node_name))

# ==================================================================================
# H001: Invalid Indexes - Observation Data for Decision Tree
# ==================================================================================
#
# This report collects observation data that enables decision tree analysis:
# - has_valid_duplicate / valid_duplicate_name: Is there a valid index on same column(s)?
# - is_pk / is_unique / constraint_name: Does it back a constraint (UNIQUE / PK)?
# - table_row_estimate: Is the table small (< 10K rows)?
#
# The JSON report contains ONLY observations (raw data).
# Recommendations (DROP, RECREATE, UNCERTAIN) are computed at render time:
# - CLI: cli/lib/checkup.ts -> getInvalidIndexRecommendation()
# - UI: Grafana dashboard or web template applies the same logic
#
# ==================================================================================

# H001 collects observation data only; recommendations (DROP, RECREATE, UNCERTAIN)
# are computed at render time by CLI (cli/lib/checkup.ts -> getInvalidIndexRecommendation())
# and UI (Grafana / web template) using the same decision tree.
def generate_h001_invalid_indexes_report(self, cluster: str = "local", node_name: str = "node-01") -> Dict[
str, Any]:
"""
Expand Down
20 changes: 0 additions & 20 deletions tests/monitoring_flask_backend/test_promql_escape_integration.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,6 @@
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', '..', 'monitoring_flask_backend'))


# ---------------------------------------------------------------------------
# Shared helpers
# ---------------------------------------------------------------------------

def _make_prom_mock(query_results=None):
"""Return a mock PrometheusConnect with empty results by default."""
mock_prom = MagicMock()
Expand All @@ -41,10 +37,6 @@ def _captured_queries(mock_prom):
return queries


# ---------------------------------------------------------------------------
# Fixtures
# ---------------------------------------------------------------------------

@pytest.fixture(autouse=True)
def isolate_app_module():
"""Force a fresh import of app for each test to avoid cross-test state."""
Expand All @@ -63,10 +55,6 @@ def client():
yield c


# ===========================================================================
# get_pgss_metrics_csv (/pgss_metrics/csv)
# ===========================================================================

class TestPgssMetricsCsvPromqlEscape:
"""Verify that /pgss_metrics/csv correctly escapes PromQL parameters."""

Expand Down Expand Up @@ -163,10 +151,6 @@ def test_node_name_newline_escaped(self, client):
f"Raw newline found in PromQL query: {q!r}")


# ===========================================================================
# get_btree_bloat_csv (/btree_bloat/csv)
# ===========================================================================

class TestBtreeBloatCsvPromqlEscape:
"""Verify that /btree_bloat/csv correctly escapes PromQL parameters."""

Expand Down Expand Up @@ -254,10 +238,6 @@ def test_node_name_quote_escaped(self, client):
f"Unescaped quote breaks label matcher: {q!r}")


# ===========================================================================
# get_table_info_csv (/table_info/csv)
# ===========================================================================

class TestTableInfoCsvPromqlEscape:
"""Verify that /table_info/csv correctly escapes PromQL parameters."""

Expand Down
Loading
Loading