chore: import upstream snapshot with attribution
Integ / changes (push) Has been skipped
Pre-commit / pre-commit (push) Failing after 1s
CLI exit codes / changes (push) Has been skipped
Test (Install) / changes (push) Has been skipped
Test (Python) / changes (push) Has been skipped
Test (TypeScript) / changes (push) Has been skipped
CLI exit codes / cli-gate (push) Has been cancelled
Test (Install) / test-install-gate (push) Has been cancelled
Integ / integ-gate (push) Has been cancelled
Test (Python) / test-python-gate (push) Has been cancelled
Test (TypeScript) / test-typescript-gate (push) Has been cancelled
Test (Install) / python-minimal (3.12) (push) Has been cancelled
Test (Install) / python-minimal (3.11) (push) Has been cancelled
Test (Install) / python-extra (agno, mirage.agents.agno) (push) Has been cancelled
Test (Install) / python-extra (chroma, mirage.resource.chroma) (push) Has been cancelled
Test (Install) / python-extra (pdf, mirage.core.filetype.pdf) (push) Has been cancelled
Integ / integ (push) Has been cancelled
Integ / integ-database (push) Has been cancelled
Integ / integ-database-ts (push) Has been cancelled
Integ / integ-data (push) Has been cancelled
Integ / integ-ssh (push) Has been cancelled
Integ / integ-ssh-ts (push) Has been cancelled
Test (Python) / audit (push) Has been cancelled
Test (TypeScript) / test (push) Has been cancelled
Test (TypeScript) / python-fs-shim (push) Has been cancelled
CLI exit codes / Python CLI (push) Has been cancelled
CLI exit codes / TypeScript CLI (push) Has been cancelled
CLI exit codes / Cross-language snapshot interop (push) Has been cancelled
Test (Python) / test (push) Has been cancelled
Test (Python) / import-isolation (deepagents, openai, mirage.agents.openai_agents) (push) Has been cancelled
Test (Python) / import-isolation (deepagents, pydantic-ai, mirage.agents.pydantic_ai) (push) Has been cancelled
Integ / integ-ts (push) Has been cancelled
Integ / integ-fuse (push) Has been cancelled
Test (Install) / python-extra (databricks, mirage.resource.databricks_volume) (push) Has been cancelled
Test (Install) / python-extra (deepagents, mirage.agents.langchain) (push) Has been cancelled
Test (Install) / python-extra (email, mirage.resource.email) (push) Has been cancelled
Test (Install) / python-extra (fuse, mirage.fuse.mount) (push) Has been cancelled
Test (Install) / python-extra (hdf5, mirage.core.filetype.hdf5) (push) Has been cancelled
Test (Install) / python-extra (hf, mirage.resource.hf_buckets) (push) Has been cancelled
Test (Install) / python-extra (lancedb, mirage.resource.lancedb) (push) Has been cancelled
Test (Install) / python-extra (langfuse, mirage.resource.langfuse) (push) Has been cancelled
Test (Install) / python-extra (mongodb, mirage.resource.mongodb) (push) Has been cancelled
Test (Install) / python-extra (nextcloud, mirage.resource.nextcloud) (push) Has been cancelled
Test (Install) / python-extra (openai, mirage.agents.openai_agents) (push) Has been cancelled
Test (Install) / python-extra (openhands, mirage.agents.openhands, 3.12) (push) Has been cancelled
Test (Install) / python-extra (parquet, mirage.core.filetype.parquet) (push) Has been cancelled
Test (Install) / python-extra (postgres, mirage.resource.postgres) (push) Has been cancelled
Test (Install) / python-extra (pydantic-ai, mirage.agents.pydantic_ai) (push) Has been cancelled
Test (Install) / python-extra (qdrant, mirage.resource.qdrant) (push) Has been cancelled
Test (Install) / python-extra (redis, mirage.resource.redis) (push) Has been cancelled
Test (Install) / python-extra (s3, mirage.resource.s3) (push) Has been cancelled
Test (Install) / python-extra (ssh, mirage.resource.ssh) (push) Has been cancelled
Test (Install) / ts-minimal (push) Has been cancelled
Integ / changes (push) Has been skipped
Pre-commit / pre-commit (push) Failing after 1s
CLI exit codes / changes (push) Has been skipped
Test (Install) / changes (push) Has been skipped
Test (Python) / changes (push) Has been skipped
Test (TypeScript) / changes (push) Has been skipped
CLI exit codes / cli-gate (push) Has been cancelled
Test (Install) / test-install-gate (push) Has been cancelled
Integ / integ-gate (push) Has been cancelled
Test (Python) / test-python-gate (push) Has been cancelled
Test (TypeScript) / test-typescript-gate (push) Has been cancelled
Test (Install) / python-minimal (3.12) (push) Has been cancelled
Test (Install) / python-minimal (3.11) (push) Has been cancelled
Test (Install) / python-extra (agno, mirage.agents.agno) (push) Has been cancelled
Test (Install) / python-extra (chroma, mirage.resource.chroma) (push) Has been cancelled
Test (Install) / python-extra (pdf, mirage.core.filetype.pdf) (push) Has been cancelled
Integ / integ (push) Has been cancelled
Integ / integ-database (push) Has been cancelled
Integ / integ-database-ts (push) Has been cancelled
Integ / integ-data (push) Has been cancelled
Integ / integ-ssh (push) Has been cancelled
Integ / integ-ssh-ts (push) Has been cancelled
Test (Python) / audit (push) Has been cancelled
Test (TypeScript) / test (push) Has been cancelled
Test (TypeScript) / python-fs-shim (push) Has been cancelled
CLI exit codes / Python CLI (push) Has been cancelled
CLI exit codes / TypeScript CLI (push) Has been cancelled
CLI exit codes / Cross-language snapshot interop (push) Has been cancelled
Test (Python) / test (push) Has been cancelled
Test (Python) / import-isolation (deepagents, openai, mirage.agents.openai_agents) (push) Has been cancelled
Test (Python) / import-isolation (deepagents, pydantic-ai, mirage.agents.pydantic_ai) (push) Has been cancelled
Integ / integ-ts (push) Has been cancelled
Integ / integ-fuse (push) Has been cancelled
Test (Install) / python-extra (databricks, mirage.resource.databricks_volume) (push) Has been cancelled
Test (Install) / python-extra (deepagents, mirage.agents.langchain) (push) Has been cancelled
Test (Install) / python-extra (email, mirage.resource.email) (push) Has been cancelled
Test (Install) / python-extra (fuse, mirage.fuse.mount) (push) Has been cancelled
Test (Install) / python-extra (hdf5, mirage.core.filetype.hdf5) (push) Has been cancelled
Test (Install) / python-extra (hf, mirage.resource.hf_buckets) (push) Has been cancelled
Test (Install) / python-extra (lancedb, mirage.resource.lancedb) (push) Has been cancelled
Test (Install) / python-extra (langfuse, mirage.resource.langfuse) (push) Has been cancelled
Test (Install) / python-extra (mongodb, mirage.resource.mongodb) (push) Has been cancelled
Test (Install) / python-extra (nextcloud, mirage.resource.nextcloud) (push) Has been cancelled
Test (Install) / python-extra (openai, mirage.agents.openai_agents) (push) Has been cancelled
Test (Install) / python-extra (openhands, mirage.agents.openhands, 3.12) (push) Has been cancelled
Test (Install) / python-extra (parquet, mirage.core.filetype.parquet) (push) Has been cancelled
Test (Install) / python-extra (postgres, mirage.resource.postgres) (push) Has been cancelled
Test (Install) / python-extra (pydantic-ai, mirage.agents.pydantic_ai) (push) Has been cancelled
Test (Install) / python-extra (qdrant, mirage.resource.qdrant) (push) Has been cancelled
Test (Install) / python-extra (redis, mirage.resource.redis) (push) Has been cancelled
Test (Install) / python-extra (s3, mirage.resource.s3) (push) Has been cancelled
Test (Install) / python-extra (ssh, mirage.resource.ssh) (push) Has been cancelled
Test (Install) / ts-minimal (push) Has been cancelled
This commit is contained in:
@@ -0,0 +1,13 @@
|
||||
# ========= Copyright 2026 @ Strukto.AI All Rights Reserved. =========
|
||||
# Licensed 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.
|
||||
# ========= Copyright 2026 @ Strukto.AI All Rights Reserved. =========
|
||||
@@ -0,0 +1,338 @@
|
||||
# ========= Copyright 2026 @ Strukto.AI All Rights Reserved. =========
|
||||
# Licensed 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.
|
||||
# ========= Copyright 2026 @ Strukto.AI All Rights Reserved. =========
|
||||
|
||||
from unittest.mock import AsyncMock, MagicMock
|
||||
|
||||
import pytest
|
||||
|
||||
from mirage.core.postgres import _client
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def mock_conn():
|
||||
conn = MagicMock()
|
||||
conn.fetch = AsyncMock()
|
||||
conn.fetchval = AsyncMock()
|
||||
conn.fetchrow = AsyncMock()
|
||||
return conn
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_list_schemas(mock_conn):
|
||||
mock_conn.fetch.return_value = [
|
||||
{
|
||||
"schema_name": "analytics"
|
||||
},
|
||||
{
|
||||
"schema_name": "public"
|
||||
},
|
||||
]
|
||||
result = await _client.list_schemas(mock_conn, None)
|
||||
assert result == ["analytics", "public"]
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_list_schemas_allowlist(mock_conn):
|
||||
mock_conn.fetch.return_value = [
|
||||
{
|
||||
"schema_name": "public"
|
||||
},
|
||||
{
|
||||
"schema_name": "analytics"
|
||||
},
|
||||
]
|
||||
result = await _client.list_schemas(mock_conn, ["public"])
|
||||
assert result == ["public"]
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_list_tables(mock_conn):
|
||||
mock_conn.fetch.return_value = [
|
||||
{
|
||||
"table_name": "v1"
|
||||
},
|
||||
{
|
||||
"table_name": "v2"
|
||||
},
|
||||
]
|
||||
result = await _client.list_tables(mock_conn, "public")
|
||||
assert result == ["v1", "v2"]
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_list_views(mock_conn):
|
||||
mock_conn.fetch.return_value = [
|
||||
{
|
||||
"table_name": "v1"
|
||||
},
|
||||
{
|
||||
"table_name": "v2"
|
||||
},
|
||||
]
|
||||
result = await _client.list_views(mock_conn, "public")
|
||||
assert result == ["v1", "v2"]
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_list_matviews(mock_conn):
|
||||
mock_conn.fetch.return_value = [{"name": "mv1"}]
|
||||
result = await _client.list_matviews(mock_conn, "public")
|
||||
assert result == ["mv1"]
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_count_rows(mock_conn):
|
||||
mock_conn.fetchval.return_value = 42
|
||||
result = await _client.count_rows(mock_conn, "public", "users")
|
||||
assert result == 42
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_estimate_size(mock_conn):
|
||||
mock_conn.fetchval.return_value = [{
|
||||
"Plan": {
|
||||
"Plan Rows": 1234,
|
||||
"Plan Width": 80
|
||||
}
|
||||
}]
|
||||
result = await _client.estimate_size(mock_conn, "public", "users")
|
||||
assert result == (1234, 80)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_estimate_size_json_string(mock_conn):
|
||||
mock_conn.fetchval.return_value = (
|
||||
'[{"Plan": {"Plan Rows": 500, "Plan Width": 64}}]')
|
||||
rows, width = await _client.estimate_size(mock_conn, "public", "v1")
|
||||
assert rows == 500
|
||||
assert width == 64
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_estimated_row_count(mock_conn):
|
||||
mock_conn.fetchval.return_value = 9999
|
||||
result = await _client.estimated_row_count(mock_conn, "public", "users")
|
||||
assert result == 9999
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_estimated_row_count_none(mock_conn):
|
||||
mock_conn.fetchval.return_value = None
|
||||
result = await _client.estimated_row_count(mock_conn, "public", "users")
|
||||
assert result == 0
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_table_size_bytes(mock_conn):
|
||||
mock_conn.fetchval.return_value = 2_097_152
|
||||
result = await _client.table_size_bytes(mock_conn, "public", "users")
|
||||
assert result == 2097152
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_fetch_rows(mock_conn):
|
||||
mock_conn.fetch.return_value = [
|
||||
{
|
||||
"id": 1,
|
||||
"name": "a"
|
||||
},
|
||||
{
|
||||
"id": 2,
|
||||
"name": "b"
|
||||
},
|
||||
]
|
||||
result = await _client.fetch_rows(mock_conn,
|
||||
"public",
|
||||
"users",
|
||||
limit=10,
|
||||
offset=0)
|
||||
assert len(result) == 2
|
||||
assert result[0]["name"] == "a"
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_fetch_columns(mock_conn):
|
||||
mock_conn.fetch.return_value = [
|
||||
{
|
||||
"column_name": "id",
|
||||
"data_type": "uuid",
|
||||
"is_nullable": "NO"
|
||||
},
|
||||
{
|
||||
"column_name": "team_id",
|
||||
"data_type": "uuid",
|
||||
"is_nullable": "YES",
|
||||
},
|
||||
]
|
||||
result = await _client.fetch_columns(mock_conn, "public", "users")
|
||||
assert result == [
|
||||
{
|
||||
"name": "id",
|
||||
"type": "uuid",
|
||||
"nullable": False
|
||||
},
|
||||
{
|
||||
"name": "team_id",
|
||||
"type": "uuid",
|
||||
"nullable": True
|
||||
},
|
||||
]
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_fetch_primary_key(mock_conn):
|
||||
mock_conn.fetch.return_value = [{"column_name": "id"}]
|
||||
result = await _client.fetch_primary_key(mock_conn, "public", "users")
|
||||
assert result == ["id"]
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_fetch_foreign_keys(mock_conn):
|
||||
mock_conn.fetch.return_value = [{
|
||||
"constraint_name": "fk1",
|
||||
"from_column": "team_id",
|
||||
"ordinal_position": 1,
|
||||
"to_schema": "public",
|
||||
"to_table": "teams",
|
||||
"to_column": "id",
|
||||
}]
|
||||
result = await _client.fetch_foreign_keys(mock_conn, "public", "users")
|
||||
assert result == [{
|
||||
"columns": ["team_id"],
|
||||
"references": {
|
||||
"schema": "public",
|
||||
"table": "teams",
|
||||
"columns": ["id"],
|
||||
},
|
||||
}]
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_fetch_foreign_keys_multi_column(mock_conn):
|
||||
mock_conn.fetch.return_value = [
|
||||
{
|
||||
"constraint_name": "fk_compound",
|
||||
"from_column": "tenant_id",
|
||||
"to_column": "tenant_id",
|
||||
"ord": 1,
|
||||
"to_schema": "public",
|
||||
"to_table": "accounts",
|
||||
},
|
||||
{
|
||||
"constraint_name": "fk_compound",
|
||||
"from_column": "user_id",
|
||||
"to_column": "id",
|
||||
"ord": 2,
|
||||
"to_schema": "public",
|
||||
"to_table": "accounts",
|
||||
},
|
||||
]
|
||||
result = await _client.fetch_foreign_keys(mock_conn, "public",
|
||||
"memberships")
|
||||
assert result == [{
|
||||
"columns": ["tenant_id", "user_id"],
|
||||
"references": {
|
||||
"schema": "public",
|
||||
"table": "accounts",
|
||||
"columns": ["tenant_id", "id"],
|
||||
},
|
||||
}]
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_fetch_indexes(mock_conn):
|
||||
mock_conn.fetch.return_value = [{
|
||||
"name": "users_email_idx",
|
||||
"unique": True,
|
||||
"columns": ["email"],
|
||||
}]
|
||||
result = await _client.fetch_indexes(mock_conn, "public", "users")
|
||||
assert result == [{
|
||||
"name": "users_email_idx",
|
||||
"columns": ["email"],
|
||||
"unique": True,
|
||||
}]
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_fetch_all_relationships(mock_conn):
|
||||
mock_conn.fetch.return_value = [{
|
||||
"constraint_name": "fk1",
|
||||
"from_schema": "public",
|
||||
"from_table": "users",
|
||||
"from_column": "team_id",
|
||||
"ordinal_position": 1,
|
||||
"to_schema": "public",
|
||||
"to_table": "teams",
|
||||
"to_column": "id",
|
||||
}]
|
||||
result = await _client.fetch_all_relationships(mock_conn, ["public"])
|
||||
assert len(result) == 1
|
||||
assert result[0]["kind"] == "many_to_one"
|
||||
assert result[0]["from"] == {
|
||||
"schema": "public",
|
||||
"table": "users",
|
||||
"columns": ["team_id"],
|
||||
}
|
||||
assert result[0]["to"] == {
|
||||
"schema": "public",
|
||||
"table": "teams",
|
||||
"columns": ["id"],
|
||||
}
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_fetch_all_relationships_multi_column(mock_conn):
|
||||
mock_conn.fetch.return_value = [
|
||||
{
|
||||
"constraint_name": "fk_compound",
|
||||
"from_schema": "public",
|
||||
"from_table": "memberships",
|
||||
"from_column": "tenant_id",
|
||||
"to_column": "tenant_id",
|
||||
"ord": 1,
|
||||
"to_schema": "public",
|
||||
"to_table": "accounts",
|
||||
},
|
||||
{
|
||||
"constraint_name": "fk_compound",
|
||||
"from_schema": "public",
|
||||
"from_table": "memberships",
|
||||
"from_column": "user_id",
|
||||
"to_column": "id",
|
||||
"ord": 2,
|
||||
"to_schema": "public",
|
||||
"to_table": "accounts",
|
||||
},
|
||||
]
|
||||
result = await _client.fetch_all_relationships(mock_conn, ["public"])
|
||||
assert len(result) == 1
|
||||
assert result[0]["kind"] == "many_to_one"
|
||||
assert result[0]["from"] == {
|
||||
"schema": "public",
|
||||
"table": "memberships",
|
||||
"columns": ["tenant_id", "user_id"],
|
||||
}
|
||||
assert result[0]["to"] == {
|
||||
"schema": "public",
|
||||
"table": "accounts",
|
||||
"columns": ["tenant_id", "id"],
|
||||
}
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_fetch_all_relationships_empty_schemas(mock_conn):
|
||||
result = await _client.fetch_all_relationships(mock_conn, [])
|
||||
assert result == []
|
||||
mock_conn.fetch.assert_not_called()
|
||||
@@ -0,0 +1,79 @@
|
||||
# ========= Copyright 2026 @ Strukto.AI All Rights Reserved. =========
|
||||
# Licensed 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.
|
||||
# ========= Copyright 2026 @ Strukto.AI All Rights Reserved. =========
|
||||
|
||||
from unittest.mock import AsyncMock, patch
|
||||
|
||||
import pytest
|
||||
|
||||
from mirage.accessor.postgres import PostgresAccessor
|
||||
from mirage.cache.index.ram import RAMIndexCacheStore
|
||||
from mirage.core.postgres.glob import resolve_glob
|
||||
from mirage.resource.postgres.config import PostgresConfig
|
||||
from mirage.types import PathSpec
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def index():
|
||||
return RAMIndexCacheStore()
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def accessor():
|
||||
return PostgresAccessor(PostgresConfig(dsn="postgres://localhost/db"))
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_resolve_glob_str_path(accessor, index):
|
||||
result = await resolve_glob(accessor, ["/public/tables/users"], index)
|
||||
assert len(result) == 1
|
||||
assert result[0].virtual == "/public/tables/users"
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_resolve_glob_resolved_pathspec(accessor, index):
|
||||
p = PathSpec(resource_path="public/tables/users",
|
||||
virtual="/public/tables/users",
|
||||
directory="/public/tables",
|
||||
resolved=True)
|
||||
result = await resolve_glob(accessor, [p], index)
|
||||
assert result == [p]
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_resolve_glob_pattern_match(accessor, index):
|
||||
with patch("mirage.core.postgres.glob.readdir",
|
||||
new_callable=AsyncMock,
|
||||
return_value=[
|
||||
"/public/tables/users", "/public/tables/orders",
|
||||
"/public/tables/teams"
|
||||
]):
|
||||
p = PathSpec(resource_path="public/tables/u*",
|
||||
virtual="/public/tables/u*",
|
||||
directory="/public/tables",
|
||||
pattern="u*",
|
||||
resolved=False)
|
||||
result = await resolve_glob(accessor, [p], index)
|
||||
assert len(result) == 1
|
||||
assert result[0].virtual == "/public/tables/users"
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_resolve_glob_unresolved_no_pattern(accessor, index):
|
||||
p = PathSpec(resource_path="public/tables",
|
||||
virtual="/public/tables",
|
||||
directory="/public",
|
||||
resolved=False,
|
||||
pattern=None)
|
||||
result = await resolve_glob(accessor, [p], index)
|
||||
assert result == [p]
|
||||
@@ -0,0 +1,217 @@
|
||||
# ========= Copyright 2026 @ Strukto.AI All Rights Reserved. =========
|
||||
# Licensed 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.
|
||||
# ========= Copyright 2026 @ Strukto.AI All Rights Reserved. =========
|
||||
|
||||
import json
|
||||
from contextlib import asynccontextmanager
|
||||
from unittest.mock import AsyncMock, MagicMock, patch
|
||||
|
||||
import pytest
|
||||
|
||||
from mirage.accessor.postgres import PostgresAccessor
|
||||
from mirage.cache.index.ram import RAMIndexCacheStore
|
||||
from mirage.core.postgres.read import read
|
||||
from mirage.resource.postgres.config import PostgresConfig
|
||||
from mirage.types import PathSpec
|
||||
|
||||
|
||||
@asynccontextmanager
|
||||
async def _fake_acquire():
|
||||
yield MagicMock()
|
||||
|
||||
|
||||
def _accessor(max_read_rows: int = 10_000,
|
||||
max_read_bytes: int = 10 * 1024 * 1024,
|
||||
default_row_limit: int = 1000) -> PostgresAccessor:
|
||||
a = PostgresAccessor(
|
||||
PostgresConfig(dsn="postgres://localhost/db",
|
||||
max_read_rows=max_read_rows,
|
||||
max_read_bytes=max_read_bytes,
|
||||
default_row_limit=default_row_limit))
|
||||
pool = MagicMock()
|
||||
pool.acquire = lambda: _fake_acquire()
|
||||
a.pool = AsyncMock(return_value=pool)
|
||||
return a
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def index():
|
||||
return RAMIndexCacheStore()
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_read_database_json():
|
||||
accessor = _accessor()
|
||||
fake_doc = {
|
||||
"database": "db",
|
||||
"schemas": ["public"],
|
||||
"tables": [],
|
||||
"views": [],
|
||||
"relationships": []
|
||||
}
|
||||
with patch("mirage.core.postgres.read.build_database_json",
|
||||
new_callable=AsyncMock,
|
||||
return_value=fake_doc):
|
||||
out = await read(
|
||||
accessor,
|
||||
PathSpec(resource_path="database.json",
|
||||
virtual="/database.json",
|
||||
directory="/database.json"))
|
||||
parsed = json.loads(out)
|
||||
assert parsed == fake_doc
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_read_entity_schema_json_table():
|
||||
accessor = _accessor()
|
||||
fake_doc = {"schema": "public", "name": "users", "kind": "table"}
|
||||
with patch("mirage.core.postgres.read.build_entity_schema_json",
|
||||
new_callable=AsyncMock,
|
||||
return_value=fake_doc) as mock_fn:
|
||||
out = await read(
|
||||
accessor,
|
||||
PathSpec(resource_path="public/tables/users/schema.json",
|
||||
virtual="/public/tables/users/schema.json",
|
||||
directory="/public/tables/users/schema.json"))
|
||||
parsed = json.loads(out)
|
||||
assert parsed == fake_doc
|
||||
mock_fn.assert_awaited_once_with(accessor, "public", "users", "table")
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_read_entity_schema_json_view_kind():
|
||||
accessor = _accessor()
|
||||
fake_doc = {"schema": "public", "name": "v1", "kind": "view"}
|
||||
with patch("mirage.core.postgres.read.build_entity_schema_json",
|
||||
new_callable=AsyncMock,
|
||||
return_value=fake_doc) as mock_fn:
|
||||
await read(
|
||||
accessor,
|
||||
PathSpec(resource_path="public/views/v1/schema.json",
|
||||
virtual="/public/views/v1/schema.json",
|
||||
directory="/public/views/v1/schema.json"))
|
||||
mock_fn.assert_awaited_once_with(accessor, "public", "v1", "view")
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_read_rows_returns_jsonl():
|
||||
accessor = _accessor()
|
||||
rows = [{"id": 1, "name": "a"}, {"id": 2, "name": "b"}]
|
||||
with patch("mirage.core.postgres.read._client") as mc:
|
||||
mc.estimate_size = AsyncMock(return_value=(2, 80))
|
||||
mc.fetch_rows = AsyncMock(return_value=rows)
|
||||
out = await read(
|
||||
accessor,
|
||||
PathSpec(resource_path="public/tables/users/rows.jsonl",
|
||||
virtual="/public/tables/users/rows.jsonl",
|
||||
directory="/public/tables/users/rows.jsonl"))
|
||||
lines = out.decode().strip().split("\n")
|
||||
assert len(lines) == 2
|
||||
assert json.loads(lines[0]) == {"id": 1, "name": "a"}
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_read_rows_too_many_rows_raises():
|
||||
accessor = _accessor(max_read_rows=100)
|
||||
with patch("mirage.core.postgres.read._client") as mc:
|
||||
mc.estimate_size = AsyncMock(return_value=(1_000_000, 50))
|
||||
with pytest.raises(ValueError, match="too large"):
|
||||
await read(
|
||||
accessor,
|
||||
PathSpec(resource_path="public/tables/users/rows.jsonl",
|
||||
virtual="/public/tables/users/rows.jsonl",
|
||||
directory="/public/tables/users/rows.jsonl"))
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_read_rows_too_many_bytes_raises():
|
||||
accessor = _accessor(max_read_rows=10_000_000, max_read_bytes=1024)
|
||||
with patch("mirage.core.postgres.read._client") as mc:
|
||||
mc.estimate_size = AsyncMock(return_value=(100, 100))
|
||||
with pytest.raises(ValueError, match="too large"):
|
||||
await read(
|
||||
accessor,
|
||||
PathSpec(resource_path="public/tables/users/rows.jsonl",
|
||||
virtual="/public/tables/users/rows.jsonl",
|
||||
directory="/public/tables/users/rows.jsonl"))
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_read_rows_with_explicit_limit_bypasses_guard():
|
||||
accessor = _accessor(max_read_rows=10)
|
||||
rows = [{"id": i} for i in range(5)]
|
||||
with patch("mirage.core.postgres.read._client") as mc:
|
||||
mc.fetch_rows = AsyncMock(return_value=rows)
|
||||
out = await read(accessor,
|
||||
PathSpec(
|
||||
resource_path="public/tables/users/rows.jsonl",
|
||||
virtual="/public/tables/users/rows.jsonl",
|
||||
directory="/public/tables/users/rows.jsonl"),
|
||||
limit=5,
|
||||
offset=0)
|
||||
mc.estimate_size.assert_not_called()
|
||||
lines = out.decode().strip().split("\n")
|
||||
assert len(lines) == 5
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_read_rows_with_only_offset_bypasses_guard():
|
||||
accessor = _accessor(max_read_rows=10)
|
||||
rows = [{"id": i} for i in range(3)]
|
||||
with patch("mirage.core.postgres.read._client") as mc:
|
||||
mc.fetch_rows = AsyncMock(return_value=rows)
|
||||
await read(accessor,
|
||||
PathSpec(resource_path="public/tables/users/rows.jsonl",
|
||||
virtual="/public/tables/users/rows.jsonl",
|
||||
directory="/public/tables/users/rows.jsonl"),
|
||||
offset=10)
|
||||
mc.estimate_size.assert_not_called()
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_read_rows_empty_returns_empty_bytes():
|
||||
accessor = _accessor()
|
||||
with patch("mirage.core.postgres.read._client") as mc:
|
||||
mc.estimate_size = AsyncMock(return_value=(0, 50))
|
||||
mc.fetch_rows = AsyncMock(return_value=[])
|
||||
out = await read(
|
||||
accessor,
|
||||
PathSpec(resource_path="public/tables/users/rows.jsonl",
|
||||
virtual="/public/tables/users/rows.jsonl",
|
||||
directory="/public/tables/users/rows.jsonl"))
|
||||
assert out == b""
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_read_invalid_path_raises():
|
||||
accessor = _accessor()
|
||||
with pytest.raises(FileNotFoundError):
|
||||
await read(
|
||||
accessor,
|
||||
PathSpec(resource_path="public/tables",
|
||||
virtual="/public/tables",
|
||||
directory="/public/tables"))
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_read_view_rows_uses_view_kind_in_error():
|
||||
"""Error message references views/, not tables/, for a view path."""
|
||||
accessor = _accessor(max_read_rows=10)
|
||||
with patch("mirage.core.postgres.read._client") as mc:
|
||||
mc.estimate_size = AsyncMock(return_value=(10000, 100))
|
||||
with pytest.raises(ValueError, match="views/v1"):
|
||||
await read(
|
||||
accessor,
|
||||
PathSpec(resource_path="public/views/v1/rows.jsonl",
|
||||
virtual="/public/views/v1/rows.jsonl",
|
||||
directory="/public/views/v1/rows.jsonl"))
|
||||
@@ -0,0 +1,154 @@
|
||||
# ========= Copyright 2026 @ Strukto.AI All Rights Reserved. =========
|
||||
# Licensed 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.
|
||||
# ========= Copyright 2026 @ Strukto.AI All Rights Reserved. =========
|
||||
|
||||
from contextlib import asynccontextmanager
|
||||
from unittest.mock import AsyncMock, MagicMock, patch
|
||||
|
||||
import pytest
|
||||
|
||||
from mirage.accessor.postgres import PostgresAccessor
|
||||
from mirage.cache.index.ram import RAMIndexCacheStore
|
||||
from mirage.core.postgres.readdir import is_dir_name, readdir
|
||||
from mirage.resource.postgres.config import PostgresConfig
|
||||
from mirage.types import PathSpec
|
||||
|
||||
|
||||
@asynccontextmanager
|
||||
async def _fake_acquire():
|
||||
yield MagicMock()
|
||||
|
||||
|
||||
def _accessor(schemas=None) -> PostgresAccessor:
|
||||
a = PostgresAccessor(
|
||||
PostgresConfig(dsn="postgres://localhost/db", schemas=schemas))
|
||||
pool = MagicMock()
|
||||
pool.acquire = lambda: _fake_acquire()
|
||||
a.pool = AsyncMock(return_value=pool)
|
||||
return a
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def index():
|
||||
return RAMIndexCacheStore()
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def accessor():
|
||||
return _accessor()
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_readdir_root_lists_database_json_and_schemas(accessor, index):
|
||||
with patch("mirage.core.postgres.readdir._client") as mc:
|
||||
mc.list_schemas = AsyncMock(return_value=["public", "analytics"])
|
||||
result = await readdir(
|
||||
accessor, PathSpec(resource_path="", virtual="/", directory="/"),
|
||||
index)
|
||||
assert "/database.json" in result
|
||||
assert "/public" in result
|
||||
assert "/analytics" in result
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_readdir_schema_lists_kinds(accessor, index):
|
||||
result = await readdir(
|
||||
accessor,
|
||||
PathSpec(resource_path="public",
|
||||
virtual="/public",
|
||||
directory="/public"), index)
|
||||
assert result == ["/public/tables", "/public/views"]
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_readdir_tables_kind_lists_tables(accessor, index):
|
||||
with patch("mirage.core.postgres.readdir._client") as mc:
|
||||
mc.list_tables = AsyncMock(return_value=["users", "orders"])
|
||||
result = await readdir(
|
||||
accessor,
|
||||
PathSpec(resource_path="public/tables",
|
||||
virtual="/public/tables",
|
||||
directory="/public/tables"), index)
|
||||
assert "/public/tables/users" in result
|
||||
assert "/public/tables/orders" in result
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_readdir_views_kind_unions_views_and_matviews(accessor, index):
|
||||
with patch("mirage.core.postgres.readdir._client") as mc:
|
||||
mc.list_views = AsyncMock(return_value=["customer_360"])
|
||||
mc.list_matviews = AsyncMock(return_value=["daily_revenue"])
|
||||
result = await readdir(
|
||||
accessor,
|
||||
PathSpec(resource_path="public/views",
|
||||
virtual="/public/views",
|
||||
directory="/public/views"), index)
|
||||
assert "/public/views/customer_360" in result
|
||||
assert "/public/views/daily_revenue" in result
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_readdir_entity_lists_schema_and_rows(accessor, index):
|
||||
result = await readdir(
|
||||
accessor,
|
||||
PathSpec(resource_path="public/tables/users",
|
||||
virtual="/public/tables/users",
|
||||
directory="/public/tables/users"), index)
|
||||
assert result == [
|
||||
"/public/tables/users/schema.json",
|
||||
"/public/tables/users/rows.jsonl",
|
||||
]
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_readdir_view_entity_lists_schema_and_rows(accessor, index):
|
||||
result = await readdir(
|
||||
accessor,
|
||||
PathSpec(resource_path="analytics/views/daily_revenue",
|
||||
virtual="/analytics/views/daily_revenue",
|
||||
directory="/analytics/views/daily_revenue"), index)
|
||||
assert result == [
|
||||
"/analytics/views/daily_revenue/schema.json",
|
||||
"/analytics/views/daily_revenue/rows.jsonl",
|
||||
]
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_readdir_invalid_path_raises(accessor, index):
|
||||
with pytest.raises(FileNotFoundError):
|
||||
await readdir(
|
||||
accessor,
|
||||
PathSpec(resource_path="public/tables/users/extra/foo",
|
||||
virtual="/public/tables/users/extra/foo",
|
||||
directory="/public/tables/users/extra/foo"), index)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_readdir_caches_root_listing(accessor, index):
|
||||
mock_list_schemas = AsyncMock(return_value=["public"])
|
||||
with patch("mirage.core.postgres.readdir._client") as mc:
|
||||
mc.list_schemas = mock_list_schemas
|
||||
first = await readdir(
|
||||
accessor, PathSpec(resource_path="", virtual="/", directory="/"),
|
||||
index)
|
||||
second = await readdir(
|
||||
accessor, PathSpec(resource_path="", virtual="/", directory="/"),
|
||||
index)
|
||||
assert first == second
|
||||
assert mock_list_schemas.call_count == 1
|
||||
|
||||
|
||||
def test_is_dir_name_classifies_by_extension():
|
||||
assert is_dir_name("/public/tables") is True
|
||||
assert is_dir_name("/database.json") is False
|
||||
assert is_dir_name("/public/tables/users/rows.jsonl") is False
|
||||
@@ -0,0 +1,261 @@
|
||||
# ========= Copyright 2026 @ Strukto.AI All Rights Reserved. =========
|
||||
# Licensed 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.
|
||||
# ========= Copyright 2026 @ Strukto.AI All Rights Reserved. =========
|
||||
|
||||
from contextlib import asynccontextmanager
|
||||
from unittest.mock import AsyncMock, MagicMock, patch
|
||||
|
||||
import pytest
|
||||
|
||||
from mirage.accessor.postgres import PostgresAccessor
|
||||
from mirage.core.postgres._schema_json import (_db_name_from_dsn,
|
||||
build_database_json,
|
||||
build_entity_schema_json)
|
||||
from mirage.resource.postgres.config import PostgresConfig
|
||||
|
||||
|
||||
@asynccontextmanager
|
||||
async def _fake_acquire():
|
||||
yield MagicMock()
|
||||
|
||||
|
||||
def _accessor(dsn: str = "postgres://localhost/acme_prod",
|
||||
schemas=None) -> PostgresAccessor:
|
||||
a = PostgresAccessor(PostgresConfig(dsn=dsn, schemas=schemas))
|
||||
pool = MagicMock()
|
||||
pool.acquire = lambda: _fake_acquire()
|
||||
a.pool = AsyncMock(return_value=pool)
|
||||
return a
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_build_database_json_basic():
|
||||
accessor = _accessor()
|
||||
with patch("mirage.core.postgres._schema_json._client") as mc:
|
||||
mc.list_schemas = AsyncMock(return_value=["public"])
|
||||
mc.list_tables = AsyncMock(return_value=["users", "orders"])
|
||||
mc.list_views = AsyncMock(return_value=["customer_360"])
|
||||
mc.list_matviews = AsyncMock(return_value=["daily_revenue"])
|
||||
mc.estimated_row_count = AsyncMock(side_effect=[100, 200])
|
||||
mc.table_size_bytes = AsyncMock(side_effect=[1024, 2048])
|
||||
mc.fetch_all_relationships = AsyncMock(return_value=[
|
||||
{
|
||||
"from": {
|
||||
"schema": "public",
|
||||
"table": "orders",
|
||||
"columns": ["user_id"]
|
||||
},
|
||||
"to": {
|
||||
"schema": "public",
|
||||
"table": "users",
|
||||
"columns": ["id"]
|
||||
},
|
||||
"kind": "many_to_one",
|
||||
},
|
||||
])
|
||||
result = await build_database_json(accessor)
|
||||
|
||||
assert result["database"] == "acme_prod"
|
||||
assert result["schemas"] == ["public"]
|
||||
assert result["tables"] == [
|
||||
{
|
||||
"schema": "public",
|
||||
"name": "users",
|
||||
"row_count_estimate": 100,
|
||||
"size_bytes_estimate": 1024,
|
||||
},
|
||||
{
|
||||
"schema": "public",
|
||||
"name": "orders",
|
||||
"row_count_estimate": 200,
|
||||
"size_bytes_estimate": 2048,
|
||||
},
|
||||
]
|
||||
assert result["views"] == [
|
||||
{
|
||||
"schema": "public",
|
||||
"name": "customer_360",
|
||||
"kind": "view"
|
||||
},
|
||||
{
|
||||
"schema": "public",
|
||||
"name": "daily_revenue",
|
||||
"kind": "materialized"
|
||||
},
|
||||
]
|
||||
assert len(result["relationships"]) == 1
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_build_database_json_empty():
|
||||
accessor = _accessor()
|
||||
with patch("mirage.core.postgres._schema_json._client") as mc:
|
||||
mc.list_schemas = AsyncMock(return_value=[])
|
||||
mc.fetch_all_relationships = AsyncMock(return_value=[])
|
||||
result = await build_database_json(accessor)
|
||||
assert result["schemas"] == []
|
||||
assert result["tables"] == []
|
||||
assert result["views"] == []
|
||||
assert result["relationships"] == []
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_build_entity_schema_json_table_with_pk_and_fk():
|
||||
accessor = _accessor()
|
||||
with patch("mirage.core.postgres._schema_json._client") as mc:
|
||||
mc.fetch_columns = AsyncMock(return_value=[
|
||||
{
|
||||
"name": "id",
|
||||
"type": "uuid",
|
||||
"nullable": False
|
||||
},
|
||||
{
|
||||
"name": "team_id",
|
||||
"type": "uuid",
|
||||
"nullable": True
|
||||
},
|
||||
{
|
||||
"name": "email",
|
||||
"type": "text",
|
||||
"nullable": False
|
||||
},
|
||||
])
|
||||
mc.fetch_primary_key = AsyncMock(return_value=["id"])
|
||||
mc.fetch_foreign_keys = AsyncMock(return_value=[
|
||||
{
|
||||
"columns": ["team_id"],
|
||||
"references": {
|
||||
"schema": "public",
|
||||
"table": "teams",
|
||||
"columns": ["id"],
|
||||
},
|
||||
},
|
||||
])
|
||||
mc.fetch_indexes = AsyncMock(return_value=[
|
||||
{
|
||||
"name": "users_email_idx",
|
||||
"columns": ["email"],
|
||||
"unique": True
|
||||
},
|
||||
])
|
||||
mc.estimated_row_count = AsyncMock(return_value=42)
|
||||
mc.table_size_bytes = AsyncMock(return_value=4096)
|
||||
result = await build_entity_schema_json(accessor, "public", "users",
|
||||
"table")
|
||||
|
||||
assert result["schema"] == "public"
|
||||
assert result["name"] == "users"
|
||||
assert result["kind"] == "table"
|
||||
assert result["row_count_estimate"] == 42
|
||||
assert result["size_bytes_estimate"] == 4096
|
||||
assert result["primary_key"] == ["id"]
|
||||
cols_by_name = {c["name"]: c for c in result["columns"]}
|
||||
assert cols_by_name["id"].get("primary_key") is True
|
||||
assert "primary_key" not in cols_by_name["team_id"]
|
||||
assert cols_by_name["team_id"]["references"] == {
|
||||
"schema": "public",
|
||||
"table": "teams",
|
||||
"column": "id",
|
||||
}
|
||||
assert "references" not in cols_by_name["email"]
|
||||
assert result["foreign_keys"][0]["columns"] == ["team_id"]
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_build_entity_schema_json_view_kind():
|
||||
accessor = _accessor()
|
||||
with patch("mirage.core.postgres._schema_json._client") as mc:
|
||||
mc.fetch_columns = AsyncMock(return_value=[
|
||||
{
|
||||
"name": "team",
|
||||
"type": "text",
|
||||
"nullable": True
|
||||
},
|
||||
])
|
||||
mc.fetch_primary_key = AsyncMock(return_value=[])
|
||||
mc.fetch_foreign_keys = AsyncMock(return_value=[])
|
||||
mc.fetch_indexes = AsyncMock(return_value=[])
|
||||
mc.estimated_row_count = AsyncMock(return_value=0)
|
||||
mc.table_size_bytes = AsyncMock(return_value=0)
|
||||
result = await build_entity_schema_json(accessor, "public",
|
||||
"user_summary", "view")
|
||||
assert result["kind"] == "view"
|
||||
assert result["primary_key"] == []
|
||||
assert result["columns"][0] == {
|
||||
"name": "team",
|
||||
"type": "text",
|
||||
"nullable": True,
|
||||
}
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_build_entity_schema_json_multi_column_fk():
|
||||
accessor = _accessor()
|
||||
with patch("mirage.core.postgres._schema_json._client") as mc:
|
||||
mc.fetch_columns = AsyncMock(return_value=[
|
||||
{
|
||||
"name": "tenant_id",
|
||||
"type": "uuid",
|
||||
"nullable": False
|
||||
},
|
||||
{
|
||||
"name": "user_id",
|
||||
"type": "uuid",
|
||||
"nullable": False
|
||||
},
|
||||
])
|
||||
mc.fetch_primary_key = AsyncMock(return_value=["tenant_id", "user_id"])
|
||||
mc.fetch_foreign_keys = AsyncMock(return_value=[
|
||||
{
|
||||
"columns": ["tenant_id", "user_id"],
|
||||
"references": {
|
||||
"schema": "public",
|
||||
"table": "accounts",
|
||||
"columns": ["tenant_id", "id"],
|
||||
},
|
||||
},
|
||||
])
|
||||
mc.fetch_indexes = AsyncMock(return_value=[])
|
||||
mc.estimated_row_count = AsyncMock(return_value=0)
|
||||
mc.table_size_bytes = AsyncMock(return_value=0)
|
||||
result = await build_entity_schema_json(accessor, "public",
|
||||
"memberships", "table")
|
||||
cols = {c["name"]: c for c in result["columns"]}
|
||||
assert cols["tenant_id"]["references"] == {
|
||||
"schema": "public",
|
||||
"table": "accounts",
|
||||
"column": "tenant_id",
|
||||
}
|
||||
assert cols["user_id"]["references"] == {
|
||||
"schema": "public",
|
||||
"table": "accounts",
|
||||
"column": "id",
|
||||
}
|
||||
|
||||
|
||||
def test_db_name_from_dsn_simple():
|
||||
assert _db_name_from_dsn("postgres://localhost/acme_prod") == "acme_prod"
|
||||
|
||||
|
||||
def test_db_name_from_dsn_with_query():
|
||||
assert _db_name_from_dsn(
|
||||
"postgres://localhost/acme?sslmode=require") == "acme"
|
||||
|
||||
|
||||
def test_db_name_from_dsn_with_user_pass():
|
||||
assert _db_name_from_dsn(
|
||||
"postgres://u:p@db.example.com:5432/myapp") == "myapp"
|
||||
|
||||
|
||||
def test_db_name_from_dsn_no_db_returns_default():
|
||||
assert _db_name_from_dsn("postgres://localhost") == "localhost"
|
||||
@@ -0,0 +1,122 @@
|
||||
# ========= Copyright 2026 @ Strukto.AI All Rights Reserved. =========
|
||||
# Licensed 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.
|
||||
# ========= Copyright 2026 @ Strukto.AI All Rights Reserved. =========
|
||||
|
||||
from mirage.core.postgres.scope import detect_scope
|
||||
from mirage.types import PathSpec
|
||||
|
||||
|
||||
def _ps(p: str) -> PathSpec:
|
||||
return PathSpec(virtual=p, directory=p, resource_path=p.strip("/"))
|
||||
|
||||
|
||||
def test_root():
|
||||
s = detect_scope(_ps("/"))
|
||||
assert s.level == "root"
|
||||
assert s.schema is None
|
||||
assert s.resource_path == "/"
|
||||
|
||||
|
||||
def test_root_empty_path():
|
||||
s = detect_scope(_ps(""))
|
||||
assert s.level == "root"
|
||||
|
||||
|
||||
def test_database_json():
|
||||
s = detect_scope(_ps("/database.json"))
|
||||
assert s.level == "database_json"
|
||||
assert s.file == "database.json"
|
||||
|
||||
|
||||
def test_schema():
|
||||
s = detect_scope(_ps("/public"))
|
||||
assert s.level == "schema"
|
||||
assert s.schema == "public"
|
||||
|
||||
|
||||
def test_schema_with_trailing_slash():
|
||||
s = detect_scope(_ps("/public/"))
|
||||
assert s.level == "schema"
|
||||
assert s.schema == "public"
|
||||
|
||||
|
||||
def test_kind_tables():
|
||||
s = detect_scope(_ps("/public/tables"))
|
||||
assert s.level == "kind"
|
||||
assert s.schema == "public"
|
||||
assert s.kind == "tables"
|
||||
|
||||
|
||||
def test_kind_views():
|
||||
s = detect_scope(_ps("/analytics/views"))
|
||||
assert s.level == "kind"
|
||||
assert s.schema == "analytics"
|
||||
assert s.kind == "views"
|
||||
|
||||
|
||||
def test_entity_table():
|
||||
s = detect_scope(_ps("/public/tables/users"))
|
||||
assert s.level == "entity"
|
||||
assert s.schema == "public"
|
||||
assert s.kind == "tables"
|
||||
assert s.entity == "users"
|
||||
|
||||
|
||||
def test_entity_view():
|
||||
s = detect_scope(_ps("/analytics/views/daily_revenue"))
|
||||
assert s.level == "entity"
|
||||
assert s.kind == "views"
|
||||
assert s.entity == "daily_revenue"
|
||||
|
||||
|
||||
def test_entity_schema_file():
|
||||
s = detect_scope(_ps("/public/tables/users/schema.json"))
|
||||
assert s.level == "entity_schema"
|
||||
assert s.schema == "public"
|
||||
assert s.kind == "tables"
|
||||
assert s.entity == "users"
|
||||
assert s.file == "schema.json"
|
||||
|
||||
|
||||
def test_entity_rows_file():
|
||||
s = detect_scope(_ps("/public/tables/users/rows.jsonl"))
|
||||
assert s.level == "entity_rows"
|
||||
assert s.schema == "public"
|
||||
assert s.entity == "users"
|
||||
assert s.file == "rows.jsonl"
|
||||
|
||||
|
||||
def test_view_entity_schema_file():
|
||||
s = detect_scope(_ps("/analytics/views/daily_revenue/schema.json"))
|
||||
assert s.level == "entity_schema"
|
||||
assert s.kind == "views"
|
||||
|
||||
|
||||
def test_invalid_kind_segment():
|
||||
s = detect_scope(_ps("/public/sequences"))
|
||||
assert s.level == "invalid"
|
||||
|
||||
|
||||
def test_invalid_too_deep():
|
||||
s = detect_scope(_ps("/public/tables/users/extra/foo"))
|
||||
assert s.level == "invalid"
|
||||
|
||||
|
||||
def test_invalid_unknown_file():
|
||||
s = detect_scope(_ps("/public/tables/users/data.jsonl"))
|
||||
assert s.level == "invalid"
|
||||
|
||||
|
||||
def test_invalid_kind_in_third_position():
|
||||
s = detect_scope(_ps("/public/wrong_kind/foo"))
|
||||
assert s.level == "invalid"
|
||||
@@ -0,0 +1,219 @@
|
||||
# ========= Copyright 2026 @ Strukto.AI All Rights Reserved. =========
|
||||
# Licensed 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.
|
||||
# ========= Copyright 2026 @ Strukto.AI All Rights Reserved. =========
|
||||
|
||||
from contextlib import asynccontextmanager
|
||||
from unittest.mock import AsyncMock, MagicMock, patch
|
||||
|
||||
import pytest
|
||||
|
||||
from mirage.accessor.postgres import PostgresAccessor
|
||||
from mirage.core.postgres.search import (format_grep_results, search_database,
|
||||
search_entity, search_kind,
|
||||
search_schema)
|
||||
from mirage.resource.postgres.config import PostgresConfig
|
||||
|
||||
|
||||
@asynccontextmanager
|
||||
async def _fake_acquire(conn=None):
|
||||
yield conn or MagicMock()
|
||||
|
||||
|
||||
def _accessor(schemas=None) -> PostgresAccessor:
|
||||
a = PostgresAccessor(
|
||||
PostgresConfig(dsn="postgres://localhost/db", schemas=schemas))
|
||||
pool = MagicMock()
|
||||
pool.acquire = lambda: _fake_acquire()
|
||||
a.pool = AsyncMock(return_value=pool)
|
||||
return a
|
||||
|
||||
|
||||
def _accessor_with_conn(conn) -> PostgresAccessor:
|
||||
a = PostgresAccessor(PostgresConfig(dsn="postgres://localhost/db"))
|
||||
pool = MagicMock()
|
||||
pool.acquire = lambda: _fake_acquire(conn)
|
||||
a.pool = AsyncMock(return_value=pool)
|
||||
return a
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_search_entity_returns_matching_rows():
|
||||
conn = MagicMock()
|
||||
conn.fetch = AsyncMock(side_effect=[
|
||||
# _text_columns response
|
||||
[{
|
||||
"column_name": "name"
|
||||
}, {
|
||||
"column_name": "email"
|
||||
}],
|
||||
# final query response
|
||||
[{
|
||||
"id": 1,
|
||||
"name": "alice"
|
||||
}, {
|
||||
"id": 2,
|
||||
"name": "alex"
|
||||
}],
|
||||
])
|
||||
accessor = _accessor_with_conn(conn)
|
||||
rows = await search_entity(accessor,
|
||||
"public",
|
||||
"tables",
|
||||
"users",
|
||||
"al",
|
||||
limit=100)
|
||||
assert len(rows) == 2
|
||||
assert rows[0]["name"] == "alice"
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_search_entity_no_text_columns_returns_empty():
|
||||
conn = MagicMock()
|
||||
conn.fetch = AsyncMock(return_value=[])
|
||||
accessor = _accessor_with_conn(conn)
|
||||
rows = await search_entity(accessor,
|
||||
"public",
|
||||
"tables",
|
||||
"ints",
|
||||
"x",
|
||||
limit=10)
|
||||
assert rows == []
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_search_entity_builds_or_clause():
|
||||
conn = MagicMock()
|
||||
conn.fetch = AsyncMock(side_effect=[
|
||||
[{
|
||||
"column_name": "a"
|
||||
}, {
|
||||
"column_name": "b"
|
||||
}, {
|
||||
"column_name": "c"
|
||||
}],
|
||||
[],
|
||||
])
|
||||
accessor = _accessor_with_conn(conn)
|
||||
await search_entity(accessor, "public", "tables", "t1", "pat", limit=5)
|
||||
final_call_sql = conn.fetch.await_args_list[1].args[0]
|
||||
assert "ILIKE" in final_call_sql
|
||||
assert final_call_sql.count("ILIKE") == 3
|
||||
assert "$1" in final_call_sql
|
||||
assert "LIMIT $2" in final_call_sql
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_search_kind_iterates_tables():
|
||||
accessor = _accessor()
|
||||
with patch("mirage.core.postgres.search._client") as mc, \
|
||||
patch("mirage.core.postgres.search.search_entity",
|
||||
new_callable=AsyncMock) as mock_entity:
|
||||
mc.list_tables = AsyncMock(return_value=["t1", "t2", "t3"])
|
||||
mock_entity.side_effect = [
|
||||
[{
|
||||
"id": 1
|
||||
}], # t1 has matches
|
||||
[], # t2 empty
|
||||
[{
|
||||
"id": 9
|
||||
}], # t3 has matches
|
||||
]
|
||||
result = await search_kind(accessor, "public", "tables", "x", limit=10)
|
||||
assert len(result) == 2
|
||||
assert result[0] == ("public", "tables", "t1", [{"id": 1}])
|
||||
assert result[1] == ("public", "tables", "t3", [{"id": 9}])
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_search_kind_views_unions_views_and_matviews():
|
||||
accessor = _accessor()
|
||||
with patch("mirage.core.postgres.search._client") as mc, \
|
||||
patch("mirage.core.postgres.search.search_entity",
|
||||
new_callable=AsyncMock, return_value=[]) as mock_entity:
|
||||
mc.list_views = AsyncMock(return_value=["v1"])
|
||||
mc.list_matviews = AsyncMock(return_value=["mv1"])
|
||||
await search_kind(accessor, "public", "views", "x", limit=10)
|
||||
called_entities = sorted(c.args[3] for c in mock_entity.await_args_list)
|
||||
assert called_entities == ["mv1", "v1"]
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_search_schema_visits_both_kinds():
|
||||
accessor = _accessor()
|
||||
with patch("mirage.core.postgres.search.search_kind",
|
||||
new_callable=AsyncMock,
|
||||
side_effect=[
|
||||
[("public", "tables", "t1", [{
|
||||
"id": 1
|
||||
}])],
|
||||
[("public", "views", "v1", [{
|
||||
"id": 2
|
||||
}])],
|
||||
]) as mock_kind:
|
||||
result = await search_schema(accessor, "public", "x", limit=10)
|
||||
assert len(result) == 2
|
||||
assert mock_kind.await_args_list[0].args == (accessor, "public", "tables",
|
||||
"x", 10)
|
||||
assert mock_kind.await_args_list[1].args == (accessor, "public", "views",
|
||||
"x", 10)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_search_database_iterates_schemas():
|
||||
accessor = _accessor()
|
||||
with patch("mirage.core.postgres.search._client") as mc, \
|
||||
patch("mirage.core.postgres.search.search_schema",
|
||||
new_callable=AsyncMock,
|
||||
side_effect=[
|
||||
[("public", "tables", "t1", [{"id": 1}])],
|
||||
[("analytics", "tables", "t2", [{"id": 2}])],
|
||||
]) as mock_schema:
|
||||
mc.list_schemas = AsyncMock(return_value=["public", "analytics"])
|
||||
result = await search_database(accessor, "x", limit=10)
|
||||
assert len(result) == 2
|
||||
assert mock_schema.await_count == 2
|
||||
|
||||
|
||||
def test_format_grep_results():
|
||||
results = [
|
||||
("public", "tables", "users", [{
|
||||
"id": 1,
|
||||
"name": "a"
|
||||
}]),
|
||||
("public", "views", "v1", [{
|
||||
"x": 9
|
||||
}]),
|
||||
]
|
||||
lines = format_grep_results(results)
|
||||
assert len(lines) == 2
|
||||
assert lines[0].startswith("public/tables/users/rows.jsonl:")
|
||||
assert "{\"id\":1,\"name\":\"a\"}" in lines[0]
|
||||
assert lines[1].startswith("public/views/v1/rows.jsonl:")
|
||||
|
||||
|
||||
def test_format_grep_results_empty():
|
||||
assert format_grep_results([]) == []
|
||||
|
||||
|
||||
def test_format_grep_results_multiple_rows_per_entity():
|
||||
results = [
|
||||
("s", "tables", "t", [{
|
||||
"x": 1
|
||||
}, {
|
||||
"x": 2
|
||||
}, {
|
||||
"x": 3
|
||||
}]),
|
||||
]
|
||||
lines = format_grep_results(results)
|
||||
assert len(lines) == 3
|
||||
@@ -0,0 +1,258 @@
|
||||
# ========= Copyright 2026 @ Strukto.AI All Rights Reserved. =========
|
||||
# Licensed 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.
|
||||
# ========= Copyright 2026 @ Strukto.AI All Rights Reserved. =========
|
||||
|
||||
from contextlib import asynccontextmanager
|
||||
from unittest.mock import AsyncMock, MagicMock, patch
|
||||
|
||||
import pytest
|
||||
|
||||
from mirage.accessor.postgres import PostgresAccessor
|
||||
from mirage.cache.index.ram import RAMIndexCacheStore
|
||||
from mirage.core.postgres.stat import stat
|
||||
from mirage.resource.postgres.config import PostgresConfig
|
||||
from mirage.types import FileType, PathSpec
|
||||
from mirage.utils.key_prefix import mount_key
|
||||
|
||||
|
||||
@asynccontextmanager
|
||||
async def _fake_acquire():
|
||||
yield MagicMock()
|
||||
|
||||
|
||||
def _accessor() -> PostgresAccessor:
|
||||
a = PostgresAccessor(PostgresConfig(dsn="postgres://localhost/db"))
|
||||
pool = MagicMock()
|
||||
pool.acquire = lambda: _fake_acquire()
|
||||
a.pool = AsyncMock(return_value=pool)
|
||||
return a
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def index():
|
||||
return RAMIndexCacheStore()
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def accessor():
|
||||
return _accessor()
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def _exists(monkeypatch):
|
||||
monkeypatch.setattr("mirage.core.postgres.stat._schema_exists",
|
||||
AsyncMock(return_value=True))
|
||||
monkeypatch.setattr("mirage.core.postgres.stat._entity_exists",
|
||||
AsyncMock(return_value=True))
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_stat_root(accessor, index):
|
||||
result = await stat(accessor,
|
||||
PathSpec(resource_path="", virtual="/", directory="/"),
|
||||
index)
|
||||
assert result.type == FileType.DIRECTORY
|
||||
assert result.name == "/"
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_stat_database_json(accessor, index):
|
||||
result = await stat(
|
||||
accessor,
|
||||
PathSpec(resource_path="database.json",
|
||||
virtual="/database.json",
|
||||
directory="/database.json"), index)
|
||||
assert result.type == FileType.JSON
|
||||
assert result.name == "database.json"
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_stat_schema(accessor, index):
|
||||
result = await stat(
|
||||
accessor,
|
||||
PathSpec(resource_path="public",
|
||||
virtual="/public",
|
||||
directory="/public"), index)
|
||||
assert result.type == FileType.DIRECTORY
|
||||
assert result.extra["schema"] == "public"
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_stat_kind_tables(accessor, index):
|
||||
result = await stat(
|
||||
accessor,
|
||||
PathSpec(resource_path="public/tables",
|
||||
virtual="/public/tables",
|
||||
directory="/public/tables"), index)
|
||||
assert result.type == FileType.DIRECTORY
|
||||
assert result.extra == {"schema": "public", "kind": "tables"}
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_stat_kind_views(accessor, index):
|
||||
result = await stat(
|
||||
accessor,
|
||||
PathSpec(resource_path="analytics/views",
|
||||
virtual="/analytics/views",
|
||||
directory="/analytics/views"), index)
|
||||
assert result.type == FileType.DIRECTORY
|
||||
assert result.extra == {"schema": "analytics", "kind": "views"}
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_stat_entity_table(accessor, index):
|
||||
result = await stat(
|
||||
accessor,
|
||||
PathSpec(resource_path="public/tables/users",
|
||||
virtual="/public/tables/users",
|
||||
directory="/public/tables/users"), index)
|
||||
assert result.type == FileType.DIRECTORY
|
||||
assert result.name == "users"
|
||||
assert result.extra == {
|
||||
"schema": "public",
|
||||
"kind": "tables",
|
||||
"name": "users"
|
||||
}
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_stat_entity_schema_json(accessor, index):
|
||||
result = await stat(
|
||||
accessor,
|
||||
PathSpec(resource_path="public/tables/users/schema.json",
|
||||
virtual="/public/tables/users/schema.json",
|
||||
directory="/public/tables/users/schema.json"), index)
|
||||
assert result.type == FileType.JSON
|
||||
assert result.name == "schema.json"
|
||||
assert result.extra == {
|
||||
"schema": "public",
|
||||
"kind": "tables",
|
||||
"name": "users"
|
||||
}
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_stat_entity_rows_jsonl(accessor, index):
|
||||
with patch("mirage.core.postgres.stat._client") as mc:
|
||||
mc.fetch_columns = AsyncMock(return_value=[
|
||||
{
|
||||
"name": "id",
|
||||
"type": "uuid",
|
||||
"nullable": False
|
||||
},
|
||||
{
|
||||
"name": "email",
|
||||
"type": "text",
|
||||
"nullable": False
|
||||
},
|
||||
])
|
||||
mc.estimated_row_count = AsyncMock(return_value=42)
|
||||
mc.table_size_bytes = AsyncMock(return_value=4096)
|
||||
result = await stat(
|
||||
accessor,
|
||||
PathSpec(resource_path="public/tables/users/rows.jsonl",
|
||||
virtual="/public/tables/users/rows.jsonl",
|
||||
directory="/public/tables/users/rows.jsonl"), index)
|
||||
assert result.type == FileType.TEXT
|
||||
assert result.name == "rows.jsonl"
|
||||
assert result.size == 4096
|
||||
assert result.fingerprint is not None
|
||||
assert len(result.fingerprint) == 64
|
||||
assert result.extra["row_count"] == 42
|
||||
assert result.extra["size_bytes"] == 4096
|
||||
assert result.extra["schema"] == "public"
|
||||
assert result.extra["kind"] == "tables"
|
||||
assert result.extra["name"] == "users"
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_stat_view_entity_rows(accessor, index):
|
||||
with patch("mirage.core.postgres.stat._client") as mc:
|
||||
mc.fetch_columns = AsyncMock(return_value=[
|
||||
{
|
||||
"name": "team",
|
||||
"type": "text",
|
||||
"nullable": True
|
||||
},
|
||||
])
|
||||
mc.estimated_row_count = AsyncMock(return_value=2)
|
||||
mc.table_size_bytes = AsyncMock(return_value=128)
|
||||
result = await stat(
|
||||
accessor,
|
||||
PathSpec(resource_path="analytics/views/daily_revenue/rows.jsonl",
|
||||
virtual="/analytics/views/daily_revenue/rows.jsonl",
|
||||
directory="/analytics/views/daily_revenue/rows.jsonl"),
|
||||
index)
|
||||
assert result.type == FileType.TEXT
|
||||
assert result.extra["kind"] == "views"
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_stat_fingerprint_changes_with_row_count(accessor, index):
|
||||
with patch("mirage.core.postgres.stat._client") as mc:
|
||||
mc.fetch_columns = AsyncMock(return_value=[
|
||||
{
|
||||
"name": "id",
|
||||
"type": "uuid",
|
||||
"nullable": False
|
||||
},
|
||||
])
|
||||
mc.table_size_bytes = AsyncMock(return_value=100)
|
||||
mc.estimated_row_count = AsyncMock(return_value=10)
|
||||
first = await stat(
|
||||
accessor,
|
||||
PathSpec(resource_path="public/tables/users/rows.jsonl",
|
||||
virtual="/public/tables/users/rows.jsonl",
|
||||
directory="/public/tables/users/rows.jsonl"), index)
|
||||
mc.estimated_row_count = AsyncMock(return_value=20)
|
||||
second = await stat(
|
||||
accessor,
|
||||
PathSpec(resource_path="public/tables/users/rows.jsonl",
|
||||
virtual="/public/tables/users/rows.jsonl",
|
||||
directory="/public/tables/users/rows.jsonl"), index)
|
||||
assert first.fingerprint != second.fingerprint
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_stat_invalid_raises(accessor, index):
|
||||
with pytest.raises(FileNotFoundError):
|
||||
await stat(
|
||||
accessor,
|
||||
PathSpec(resource_path="public/tables/users/extra/foo",
|
||||
virtual="/public/tables/users/extra/foo",
|
||||
directory="/public/tables/users/extra/foo"), index)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_stat_missing_schema_raises(accessor, index):
|
||||
with patch("mirage.core.postgres.stat._schema_exists",
|
||||
AsyncMock(return_value=False)):
|
||||
with pytest.raises(FileNotFoundError):
|
||||
await stat(
|
||||
accessor,
|
||||
PathSpec(resource_path=mount_key("/pg/__nf_missing__.txt",
|
||||
"/pg"),
|
||||
virtual="/pg/__nf_missing__.txt",
|
||||
directory="/pg/__nf_missing__.txt"), index)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_stat_missing_entity_raises(accessor, index):
|
||||
with patch("mirage.core.postgres.stat._entity_exists",
|
||||
AsyncMock(return_value=False)):
|
||||
with pytest.raises(FileNotFoundError):
|
||||
await stat(
|
||||
accessor,
|
||||
PathSpec(resource_path="public/tables/nope",
|
||||
virtual="/public/tables/nope",
|
||||
directory="/public/tables/nope"), index)
|
||||
Reference in New Issue
Block a user