Files
wehub-resource-sync a06f331eb8
CI / benchmark (push) Has been skipped
install-script / posix-syntax (push) Successful in 6m1s
CI / build-onnx (push) Failing after 6m43s
init-smoke / dry-run (push) Failing after 15m57s
security / govulncheck (push) Has been cancelled
security / trivy-fs (push) Has been cancelled
CI / test (1.26, ubuntu-latest) (push) Has been cancelled
Scorecard supply-chain security / Scorecard analysis (push) Has been cancelled
CI / test (1.26, macos-latest) (push) Has been cancelled
CI / build-windows (push) Has been cancelled
CI / lint (push) Has been cancelled
install-script / powershell-syntax (push) Has been cancelled
install-script / install (macos-14) (push) Has been cancelled
install-script / install (ubuntu-latest) (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 12:33:42 +08:00

328 lines
10 KiB
Go

package languages
import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/zzet/gortex/internal/graph"
)
func nodeByID(nodes []*graph.Node, id string) *graph.Node {
for _, n := range nodes {
if n.ID == id {
return n
}
}
return nil
}
func colNames(nodes []*graph.Node) []string {
var out []string
for _, n := range nodes {
if n.Kind == graph.KindColumn {
out = append(out, n.Name)
}
}
return out
}
// --- dbt SQL models -------------------------------------------------------
func TestDbt_SQLModel_RefSourceConfigColumns(t *testing.T) {
src := []byte(`{{ config(materialized='table', schema='analytics') }}
with base as (
select * from {{ source('raw', 'events') }}
)
select
e.event_id,
e.user_id,
count(*) as event_count
from base e
join {{ ref('dim_users') }} u on u.id = e.user_id
group by 1, 2
`)
e := NewSQLExtractor()
result, err := e.Extract("models/staging/stg_events.sql", src)
require.NoError(t, err)
model := nodeByID(result.Nodes, "dbt::model::stg_events")
require.NotNil(t, model, "model node should exist")
assert.Equal(t, graph.KindTable, model.Kind)
assert.Equal(t, "stg_events", model.Name)
assert.Equal(t, "dbt", model.Meta["framework"])
assert.Equal(t, "dbt_model", model.Meta["sql_type"])
assert.Equal(t, "model", model.Meta["resource_type"])
assert.Equal(t, "table", model.Meta["materialized"])
assert.Equal(t, "analytics", model.Meta["schema"])
// Source referenced via source() is materialised as a KindTable.
srcNode := nodeByID(result.Nodes, "dbt::source::raw.events")
require.NotNil(t, srcNode, "source node should exist")
assert.Equal(t, "dbt_source", srcNode.Meta["sql_type"])
// Columns from the model's final SELECT projection.
cols := colNames(result.Nodes)
assert.ElementsMatch(t, []string{"event_id", "user_id", "event_count"}, cols)
memberEdges := edgesOfKind(result.Edges, graph.EdgeMemberOf)
assert.Len(t, memberEdges, 3)
for _, me := range memberEdges {
assert.Equal(t, "dbt::model::stg_events", me.To)
}
// Lineage: one ref() + one source() dependency.
deps := edgesOfKind(result.Edges, graph.EdgeDependsOn)
require.Len(t, deps, 2)
targets := map[string]string{}
for _, d := range deps {
targets[d.To], _ = d.Meta["link"].(string)
}
assert.Equal(t, "ref", targets["dbt::model::dim_users"])
assert.Equal(t, "source", targets["dbt::source::raw.events"])
}
func TestDbt_SQLModel_PureSQLByPath(t *testing.T) {
// A dbt model need not contain any Jinja — path + a query-shaped
// body is enough to classify it.
src := []byte("select id, name, email from some_upstream\n")
e := NewSQLExtractor()
result, err := e.Extract("models/marts/dim_users.sql", src)
require.NoError(t, err)
model := nodeByID(result.Nodes, "dbt::model::dim_users")
require.NotNil(t, model)
assert.Equal(t, "view", model.Meta["materialized"]) // default
assert.ElementsMatch(t, []string{"id", "name", "email"}, colNames(result.Nodes))
}
func TestDbt_SQLModel_Snapshot(t *testing.T) {
src := []byte(`{% snapshot orders_snapshot %}
{{ config(target_schema='snapshots', unique_key='id', strategy='timestamp') }}
select id, status, updated_at from {{ source('jaffle', 'orders') }}
{% endsnapshot %}
`)
e := NewSQLExtractor()
result, err := e.Extract("snapshots/orders_snapshot.sql", src)
require.NoError(t, err)
model := nodeByID(result.Nodes, "dbt::model::orders_snapshot")
require.NotNil(t, model)
assert.Equal(t, "snapshot", model.Meta["resource_type"])
assert.Equal(t, "snapshot", model.Meta["materialized"])
assert.ElementsMatch(t, []string{"id", "status", "updated_at"}, colNames(result.Nodes))
deps := edgesOfKind(result.Edges, graph.EdgeDependsOn)
require.Len(t, deps, 1)
assert.Equal(t, "dbt::source::jaffle.orders", deps[0].To)
}
func TestDbt_SQLModel_TwoArgRef(t *testing.T) {
src := []byte("select 1 as id from {{ ref('analytics', 'dim_date') }}\n")
e := NewSQLExtractor()
result, err := e.Extract("models/x.sql", src)
require.NoError(t, err)
deps := edgesOfKind(result.Edges, graph.EdgeDependsOn)
require.Len(t, deps, 1)
assert.Equal(t, "dbt::model::dim_date", deps[0].To)
assert.Equal(t, "analytics", deps[0].Meta["ref_package"])
}
// --- SQLMesh SQL models ---------------------------------------------------
func TestSQLMesh_Model_BlockPropsAndLineage(t *testing.T) {
src := []byte(`MODEL (
name sushi.customers,
kind FULL,
cron '@daily',
grain customer_id,
owner 'data-team'
);
SELECT
customer_id,
cast(zip AS TEXT) AS zip_code,
status
FROM sushi.raw_customers
`)
e := NewSQLExtractor()
result, err := e.Extract("models/customers.sql", src)
require.NoError(t, err)
model := nodeByID(result.Nodes, "sqlmesh::model::sushi.customers")
require.NotNil(t, model)
assert.Equal(t, "customers", model.Name)
assert.Equal(t, "sqlmesh", model.Meta["framework"])
assert.Equal(t, "sushi", model.Meta["schema"])
assert.Equal(t, "full", model.Meta["materialized"])
assert.Equal(t, "@daily", model.Meta["cron"])
assert.Equal(t, "customer_id", model.Meta["grain"])
assert.Equal(t, "data-team", model.Meta["owner"])
assert.ElementsMatch(t, []string{"customer_id", "zip_code", "status"}, colNames(result.Nodes))
deps := edgesOfKind(result.Edges, graph.EdgeDependsOn)
require.Len(t, deps, 1)
assert.Equal(t, "sqlmesh::model::sushi.raw_customers", deps[0].To)
}
func TestSQLMesh_Model_ColumnsProperty(t *testing.T) {
src := []byte(`MODEL (
name analytics.orders,
kind INCREMENTAL_BY_TIME_RANGE (
time_column event_date
),
columns (
order_id INT,
total DECIMAL(10, 2),
created_at TIMESTAMP
)
);
SELECT order_id, total, created_at
FROM analytics.staging_orders
WHERE event_date BETWEEN @start_date AND @end_date
`)
e := NewSQLExtractor()
result, err := e.Extract("models/orders.sql", src)
require.NoError(t, err)
model := nodeByID(result.Nodes, "sqlmesh::model::analytics.orders")
require.NotNil(t, model)
assert.Equal(t, "incremental_by_time_range", model.Meta["materialized"])
// Explicit columns() property wins; data_type is carried in Meta.
cols := nodesOfKind(result.Nodes, graph.KindColumn)
require.Len(t, cols, 3)
byName := map[string]*graph.Node{}
for _, c := range cols {
byName[c.Name] = c
}
require.Contains(t, byName, "order_id")
assert.Equal(t, "INT", byName["order_id"].Meta["data_type"])
assert.Equal(t, "DECIMAL(10, 2)", byName["total"].Meta["data_type"])
deps := edgesOfKind(result.Edges, graph.EdgeDependsOn)
require.Len(t, deps, 1)
assert.Equal(t, "sqlmesh::model::analytics.staging_orders", deps[0].To)
}
// --- dbt schema YAML ------------------------------------------------------
func TestDbt_SchemaYAML_ModelsAndSources(t *testing.T) {
src := []byte(`version: 2
models:
- name: stg_events
description: "Staged event data"
config:
materialized: view
columns:
- name: event_id
description: "Primary key"
data_type: integer
tests:
- unique
- not_null
- name: user_id
data_type: integer
sources:
- name: raw
schema: raw_data
tables:
- name: events
columns:
- name: id
- name: payload
data_type: json
`)
e := NewYAMLExtractor()
result, err := e.Extract("models/staging/schema.yml", src)
require.NoError(t, err)
// Generic top-level-keys fallback must NOT have run.
assert.Empty(t, nodesOfKind(result.Nodes, graph.KindVariable))
model := nodeByID(result.Nodes, "dbt::model::stg_events")
require.NotNil(t, model)
assert.Equal(t, graph.KindTable, model.Kind)
assert.Equal(t, "view", model.Meta["materialized"])
assert.Equal(t, "Staged event data", model.Meta["description"])
eventID := nodeByID(result.Nodes, "dbt::model::stg_events::event_id")
require.NotNil(t, eventID)
assert.Equal(t, graph.KindColumn, eventID.Kind)
assert.Equal(t, "integer", eventID.Meta["data_type"])
assert.Equal(t, "Primary key", eventID.Meta["description"])
assert.ElementsMatch(t, []string{"unique", "not_null"}, eventID.Meta["tests"])
source := nodeByID(result.Nodes, "dbt::source::raw.events")
require.NotNil(t, source)
assert.Equal(t, "dbt_source", source.Meta["sql_type"])
assert.Equal(t, "raw_data", source.Meta["schema"])
payload := nodeByID(result.Nodes, "dbt::source::raw.events::payload")
require.NotNil(t, payload)
assert.Equal(t, "json", payload.Meta["data_type"])
assert.Len(t, nodesOfKind(result.Nodes, graph.KindColumn), 4)
assert.Len(t, edgesOfKind(result.Edges, graph.EdgeMemberOf), 4)
}
func TestDbt_SchemaYAML_PlainConfigFallsThrough(t *testing.T) {
// A plain config YAML must not be mistaken for a dbt schema file —
// the generic top-level-keys walker should still run.
src := []byte(`server:
port: 8080
database:
host: localhost
`)
e := NewYAMLExtractor()
result, err := e.Extract("config/app.yml", src)
require.NoError(t, err)
assert.Empty(t, nodesOfKind(result.Nodes, graph.KindTable))
vars := nodesOfKind(result.Nodes, graph.KindVariable)
assert.Len(t, vars, 2) // server, database
}
// --- classification -------------------------------------------------------
func TestClassifySQLFile(t *testing.T) {
cases := []struct {
name string
path string
src string
want string
}{
{"sqlmesh model block", "models/a.sql", "MODEL (\n name a\n);\nSELECT 1", "sqlmesh"},
{"sqlmesh leading comment", "a.sql", "-- header\nMODEL (name a);\nSELECT 1", "sqlmesh"},
{"dbt ref marker", "x.sql", "select * from {{ ref('y') }}", "dbt"},
{"dbt config marker", "x.sql", "{{ config(materialized='table') }}\nselect 1", "dbt"},
{"dbt pure sql by path", "models/dim.sql", "select id from upstream", "dbt"},
{"plain DDL not dbt", "schema.sql", "CREATE TABLE foo (id int);", ""},
{"DDL under models dir not dbt", "models/foo.sql", "CREATE TABLE foo (id int);", ""},
{"plain query outside models", "scratch.sql", "select 1", ""},
}
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
assert.Equal(t, tc.want, classifySQLFile(tc.path, []byte(tc.src)))
})
}
}
func TestSQLExtractor_NonDbtUnaffected(t *testing.T) {
// Regression guard: a plain DDL file still goes through the generic
// SQL walk and produces a KindType, not a dbt model node.
src := []byte("CREATE TABLE users (id INTEGER PRIMARY KEY);")
e := NewSQLExtractor()
result, err := e.Extract("db/schema.sql", src)
require.NoError(t, err)
assert.Len(t, nodesOfKind(result.Nodes, graph.KindType), 1)
assert.Empty(t, nodesOfKind(result.Nodes, graph.KindTable))
}