Merge pull request #10 from FlipsideCrypto/add-tests

Add testing strategy
This commit is contained in:
Mike Stepanovic 2025-03-17 14:55:07 -06:00 committed by GitHub
commit 5134ced0eb
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
9 changed files with 721 additions and 8 deletions

52
.github/workflows/dbt_test_daily.yml vendored Normal file
View File

@ -0,0 +1,52 @@
name: dbt_test_daily
run-name: dbt_test_daily
on:
workflow_dispatch:
schedule:
- cron: "30 0 * * *"
env:
USE_VARS: "${{ vars.USE_VARS }}"
DBT_PROFILES_DIR: "${{ vars.DBT_PROFILES_DIR }}"
DBT_VERSION: "${{ vars.DBT_VERSION }}"
ACCOUNT: "${{ vars.ACCOUNT }}"
ROLE: "${{ vars.ROLE }}"
USER: "${{ vars.USER }}"
PASSWORD: "${{ secrets.PASSWORD }}"
REGION: "${{ vars.REGION }}"
DATABASE: "${{ vars.DATABASE }}"
WAREHOUSE: "${{ vars.WAREHOUSE }}"
SCHEMA: "${{ vars.SCHEMA }}"
SLACK_WEBHOOK_URL: "${{ secrets.SLACK_WEBHOOK_URL }}"
concurrency:
group: ${{ github.workflow }}
jobs:
run_dbt_jobs:
runs-on: ubuntu-latest
environment:
name: workflow_prod
steps:
- uses: actions/checkout@v3
- uses: actions/setup-python@v4
with:
python-version: "${{ vars.PYTHON_VERSION }}"
cache: "pip"
- name: install dependencies
run: |
pip install -r requirements.txt
dbt deps
- name: Run DBT Jobs
run: |
dbt test -m tag:test_recency tag:test_quality --vars '{"TEST_HOURS_THRESHOLD":24}'
continue-on-error: true
- name: Log test results
run: |
python python/dbt_test_alert.py

52
.github/workflows/dbt_test_monthly.yml vendored Normal file
View File

@ -0,0 +1,52 @@
name: dbt_test_monthly
run-name: dbt_test_monthly
on:
workflow_dispatch:
schedule:
- cron: "0 10 28 * *"
env:
USE_VARS: "${{ vars.USE_VARS }}"
DBT_PROFILES_DIR: "${{ vars.DBT_PROFILES_DIR }}"
DBT_VERSION: "${{ vars.DBT_VERSION }}"
ACCOUNT: "${{ vars.ACCOUNT }}"
ROLE: "${{ vars.ROLE }}"
USER: "${{ vars.USER }}"
PASSWORD: "${{ secrets.PASSWORD }}"
REGION: "${{ vars.REGION }}"
DATABASE: "${{ vars.DATABASE }}"
WAREHOUSE: "${{ vars.WAREHOUSE }}"
SCHEMA: "${{ vars.SCHEMA }}"
SLACK_WEBHOOK_URL: "${{ secrets.SLACK_WEBHOOK_URL }}"
concurrency:
group: ${{ github.workflow }}
jobs:
run_dbt_jobs:
runs-on: ubuntu-latest
environment:
name: workflow_prod
steps:
- uses: actions/checkout@v3
- uses: actions/setup-python@v4
with:
python-version: "${{ vars.PYTHON_VERSION }}"
cache: "pip"
- name: install dependencies
run: |
pip install -r requirements.txt
dbt deps
- name: Run DBT Jobs
run: |
dbt test -m tag:test_recency tag:test_quality --vars '{"TEST_HOURS_THRESHOLD":744}'
continue-on-error: true
- name: Log test results
run: |
python python/dbt_test_alert.py

52
.github/workflows/dbt_test_recency.yml vendored Normal file
View File

@ -0,0 +1,52 @@
name: dbt_test_recency
run-name: dbt_test_recency
on:
workflow_dispatch:
schedule:
- cron: "0 */2 * * *"
env:
USE_VARS: "${{ vars.USE_VARS }}"
DBT_PROFILES_DIR: "${{ vars.DBT_PROFILES_DIR }}"
DBT_VERSION: "${{ vars.DBT_VERSION }}"
ACCOUNT: "${{ vars.ACCOUNT }}"
ROLE: "${{ vars.ROLE }}"
USER: "${{ vars.USER }}"
PASSWORD: "${{ secrets.PASSWORD }}"
REGION: "${{ vars.REGION }}"
DATABASE: "${{ vars.DATABASE }}"
WAREHOUSE: "${{ vars.WAREHOUSE }}"
SCHEMA: "${{ vars.SCHEMA }}"
SLACK_WEBHOOK_URL: "${{ secrets.SLACK_WEBHOOK_URL }}"
concurrency:
group: ${{ github.workflow }}
jobs:
run_dbt_jobs:
runs-on: ubuntu-latest
environment:
name: workflow_prod
steps:
- uses: actions/checkout@v3
- uses: actions/setup-python@v4
with:
python-version: "${{ vars.PYTHON_VERSION }}"
cache: "pip"
- name: install dependencies
run: |
pip install -r requirements.txt
dbt deps
- name: Run DBT Jobs
run: |
dbt source freshness && dbt test -m movement_models,tag:test_recency
continue-on-error: true
- name: Log test results
run: |
python python/dbt_test_alert.py

52
.github/workflows/dbt_test_weekly.yml vendored Normal file
View File

@ -0,0 +1,52 @@
name: dbt_test_weekly
run-name: dbt_test_weekly
on:
workflow_dispatch:
schedule:
- cron: "30 0 * * 1"
env:
USE_VARS: "${{ vars.USE_VARS }}"
DBT_PROFILES_DIR: "${{ vars.DBT_PROFILES_DIR }}"
DBT_VERSION: "${{ vars.DBT_VERSION }}"
ACCOUNT: "${{ vars.ACCOUNT }}"
ROLE: "${{ vars.ROLE }}"
USER: "${{ vars.USER }}"
PASSWORD: "${{ secrets.PASSWORD }}"
REGION: "${{ vars.REGION }}"
DATABASE: "${{ vars.DATABASE }}"
WAREHOUSE: "${{ vars.WAREHOUSE }}"
SCHEMA: "${{ vars.SCHEMA }}"
SLACK_WEBHOOK_URL: "${{ secrets.SLACK_WEBHOOK_URL }}"
concurrency:
group: ${{ github.workflow }}
jobs:
run_dbt_jobs:
runs-on: ubuntu-latest
environment:
name: workflow_prod
steps:
- uses: actions/checkout@v3
- uses: actions/setup-python@v4
with:
python-version: "${{ vars.PYTHON_VERSION }}"
cache: "pip"
- name: install dependencies
run: |
pip install -r requirements.txt
dbt deps
- name: Run DBT Jobs
run: |
dbt test -m tag:test_recency tag:test_quality --vars '{"TEST_HOURS_THRESHOLD":168}'
continue-on-error: true
- name: Log test results
run: |
python python/dbt_test_alert.py

View File

@ -27,8 +27,8 @@ clean-targets: # directories to be removed by `dbt clean`
- "dbt_packages"
tests:
+store_failures: true # all tests
# +where: "modified_timestamp > dateadd(hour, -{{ var('TEST_HOURS_THRESHOLD') }}, current_timestamp)"
+store_failures: true
+where: "modified_timestamp > dateadd(hour, -{{ var('TEST_HOURS_THRESHOLD', 3) }}, current_timestamp)"
on-run-start:
- "{{ create_sps() }}"
@ -71,6 +71,7 @@ vars:
OBSERV_FULL_TEST: false
START_GHA_TASKS: false
BRONZE_LOOKBACK_DAYS: '{{ env_var("BRONZE_LOOKBACK_DAYS", 3) }}'
TEST_HOURS_THRESHOLD: 3
#### STREAMLINE 2.0 BEGIN ####

View File

@ -2,6 +2,13 @@ version: 2
models:
- name: core__fact_blocks
description: '{{ doc("core__fact_blocks") }}'
tests:
- dbt_utils.recency:
datepart: hour
field: MODIFIED_TIMESTAMP
interval: 3
severity: error
tags: ['test_recency']
columns:
- name: block_number
description: '{{ doc("block_number") }}'
@ -24,6 +31,13 @@ models:
- name: core__fact_changes
description: '{{ doc("core__fact_changes") }}'
tests:
- dbt_utils.recency:
datepart: hour
field: MODIFIED_TIMESTAMP
interval: 3
severity: error
tags: ['test_recency']
columns:
- name: block_number
description: '{{ doc("block_number") }}'
@ -72,7 +86,14 @@ models:
description: '{{ doc("modified_timestamp") }}'
- name: core__fact_events
description: '{{ doc("core__fact_events") }}'
description: '{{ doc("core__fact_events") }}'
tests:
- dbt_utils.recency:
datepart: hour
field: MODIFIED_TIMESTAMP
interval: 3
severity: error
tags: ['test_recency']
columns:
- name: block_number
description: '{{ doc("block_number") }}'
@ -116,7 +137,14 @@ models:
description: '{{ doc("modified_timestamp") }}'
- name: core__fact_transactions_block_metadata
description: '{{ doc("core__fact_transactions_block_metadata") }}'
description: '{{ doc("core__fact_transactions_block_metadata") }}'
tests:
- dbt_utils.recency:
datepart: hour
field: MODIFIED_TIMESTAMP
interval: 3
severity: error
tags: ['test_recency']
columns:
- name: block_number
description: '{{ doc("block_number") }}'
@ -166,7 +194,14 @@ models:
description: '{{ doc("modified_timestamp") }}'
- name: core__fact_transactions_state_checkpoint
description: '{{ doc("core__fact_transactions_state_checkpoint") }}'
description: '{{ doc("core__fact_transactions_state_checkpoint") }}'
tests:
- dbt_utils.recency:
datepart: hour
field: MODIFIED_TIMESTAMP
interval: 3
severity: error
tags: ['test_recency']
columns:
- name: block_number
description: '{{ doc("block_number") }}'
@ -196,7 +231,14 @@ models:
description: '{{ doc("modified_timestamp") }}'
- name: core__fact_transactions
description: '{{ doc("core__fact_transactions") }}'
description: '{{ doc("core__fact_transactions") }}'
tests:
- dbt_utils.recency:
datepart: hour
field: MODIFIED_TIMESTAMP
interval: 3
severity: error
tags: ['test_recency']
columns:
- name: block_number
description: '{{ doc("block_number") }}'

View File

@ -0,0 +1,334 @@
version: 2
models:
- name: silver__blocks
config:
contract:
enforced: true
tests:
- dbt_utils.sequential_values:
column_name: block_number
interval: 1
config:
severity: error
error_if: ">100"
tags: ['test_recency']
columns:
- name: block_number
data_type: NUMBER
tests:
- not_null:
tags: ['test_quality']
- unique:
tags: ['test_quality']
- name: block_hash
data_type: VARCHAR
tests:
- not_null:
tags: ['test_quality']
- unique:
tags: ['test_quality']
- name: block_timestamp
data_type: TIMESTAMP_NTZ
tests:
- not_null:
tags: ['test_quality']
- name: tx_count_from_versions
data_type: NUMBER
tests:
- dbt_utils.expression_is_true:
expression: ">= 0"
tags: ['test_quality']
- name: block_timestamp_num
data_type: NUMBER
tests:
- not_null:
tags: ['test_quality']
- name: first_version
data_type: NUMBER
tests:
- not_null:
tags: ['test_quality']
- name: last_version
data_type: NUMBER
tests:
- not_null:
tags: ['test_quality']
- name: blocks_id
data_type: VARCHAR
- name: inserted_timestamp
data_type: TIMESTAMP_NTZ
- name: modified_timestamp
data_type: TIMESTAMP_NTZ
- name: _invocation_id
data_type: VARCHAR
- name: silver__changes
config:
contract:
enforced: true
columns:
- name: block_number
data_type: NUMBER
- name: block_timestamp
data_type: TIMESTAMP_NTZ
- name: tx_hash
data_type: VARCHAR
- name: version
data_type: NUMBER
- name: success
data_type: BOOLEAN
- name: tx_type
data_type: VARCHAR
- name: payload_function
data_type: VARCHAR
- name: change_index
data_type: NUMBER
tests:
- not_null:
tags: ['test_quality']
- dbt_utils.expression_is_true:
expression: ">= 0"
tags: ['test_quality']
- name: change_data
data_type: VARIANT
tests:
- not_null:
tags: ['test_quality']
- name: change_type
data_type: VARCHAR
tests:
- not_null:
tags: ['test_quality']
- name: address
data_type: VARCHAR
tests:
- not_null:
tags: ['test_quality']
- name: handle
data_type: VARCHAR
tests:
- not_null:
tags: ['test_quality']
- name: inner_change_type
data_type: VARCHAR
tests:
- not_null:
tags: ['test_quality']
- name: change_address
data_type: VARCHAR
tests:
- not_null:
tags: ['test_quality']
- name: change_module
data_type: VARCHAR
tests:
- not_null:
tags: ['test_quality']
- name: change_resource
data_type: VARCHAR
tests:
- not_null:
tags: ['test_quality']
- name: key
data_type: VARCHAR
tests:
- not_null:
tags: ['test_quality']
- name: value
data_type: VARCHAR
tests:
- not_null:
tags: ['test_quality']
- name: state_key_hash
data_type: VARCHAR
tests:
- not_null:
tags: ['test_quality']
- name: changes_id
data_type: VARCHAR
- name: inserted_timestamp
data_type: TIMESTAMP_NTZ
- name: modified_timestamp
data_type: TIMESTAMP_NTZ
- name: _invocation_id
data_type: VARCHAR
- name: silver__events
config:
contract:
enforced: true
columns:
- name: block_number
data_type: NUMBER
- name: block_timestamp
data_type: TIMESTAMP_NTZ
- name: tx_hash
data_type: VARCHAR
- name: version
data_type: NUMBER
- name: success
data_type: BOOLEAN
- name: tx_type
data_type: VARCHAR
- name: payload_function
data_type: VARCHAR
- name: event_index
data_type: NUMBER
- name: event_type
data_type: VARCHAR
tests:
- not_null:
tags: ['test_quality']
- name: event_address
data_type: VARCHAR
tests:
- not_null:
tags: ['test_quality']
- name: event_module
data_type: VARCHAR
tests:
- not_null:
tags: ['test_quality']
- name: event_resource
data_type: VARCHAR
tests:
- not_null:
tags: ['test_quality']
- name: event_data
data_type: VARIANT
tests:
- not_null:
tags: ['test_quality']
- name: account_address
data_type: VARCHAR
tests:
- not_null:
tags: ['test_quality']
- name: creation_number
data_type: NUMBER
tests:
- not_null:
tags: ['test_quality']
- dbt_utils.expression_is_true:
expression: ">= 0"
tags: ['test_quality']
- name: sequence_number
data_type: NUMBER
tests:
- not_null:
tags: ['test_quality']
- dbt_utils.expression_is_true:
expression: ">= 0"
tags: ['test_quality']
- name: events_id
data_type: VARCHAR
- name: inserted_timestamp
data_type: TIMESTAMP_NTZ
- name: modified_timestamp
data_type: TIMESTAMP_NTZ
- name: _invocation_id
data_type: VARCHAR
- name: silver__transactions
config:
contract:
enforced: true
columns:
- name: block_number
data_type: NUMBER
- name: block_timestamp
data_type: TIMESTAMP_NTZ
tests:
- not_null:
tags: ['test_quality']
- name: tx_hash
data_type: VARCHAR
tests:
- not_null:
tags: ['test_quality']
- unique:
tags: ['test_quality']
- name: version
data_type: NUMBER
tests:
- not_null:
tags: ['test_quality']
- dbt_utils.expression_is_true:
expression: ">= 0"
tags: ['test_quality']
- name: tx_type
data_type: VARCHAR
tests:
- not_null:
tags: ['test_quality']
- name: success
data_type: BOOLEAN
- name: sender
data_type: VARCHAR
- name: signature
data_type: VARCHAR
- name: payload
data_type: VARIANT
- name: payload_function
data_type: VARCHAR
- name: changes
data_type: VARIANT
- name: events
data_type: VARIANT
- name: gas_unit_price
data_type: NUMBER
tests:
- dbt_utils.expression_is_true:
expression: ">= 0"
tags: ['test_quality']
- name: gas_used
data_type: NUMBER
tests:
- dbt_utils.expression_is_true:
expression: ">= 0"
tags: ['test_quality']
- name: max_gas_amount
data_type: NUMBER
tests:
- dbt_utils.expression_is_true:
expression: ">= 0"
tags: ['test_quality']
- name: expiration_timestamp_secs
data_type: NUMBER
tests:
- dbt_utils.expression_is_true:
expression: ">= 0"
tags: ['test_quality']
- name: vm_status
data_type: VARCHAR
- name: state_change_hash
data_type: VARCHAR
- name: accumulator_root_hash
data_type: VARCHAR
- name: event_root_hash
data_type: VARCHAR
- name: state_checkpoint_hash
data_type: VARCHAR
- name: failed_proposer_indices
data_type: VARCHAR
- name: failed_proposer_indices
data_type: VARIANT
- name: id
data_type: VARCHAR
- name: previous_block_votes_bitvec
data_type: VARIANT
- name: proposer
data_type: VARCHAR
- name: ROUND
data_type: NUMBER
- name: data
data_type: VARIANT
- name: transactions_id
data_type: VARCHAR
- name: inserted_timestamp
data_type: TIMESTAMP_NTZ
- name: modified_timestamp
data_type: TIMESTAMP_NTZ
- name: _invocation_id
data_type: VARCHAR

View File

@ -1,6 +1,4 @@
packages:
- package: calogica/dbt_expectations
version: [">=0.4.0", "<0.9.0"]
- git: https://github.com/FlipsideCrypto/fsc-utils.git
revision: v1.32.0
- package: get-select/dbt_snowflake_query_tags

130
python/dbt_test_alert.py Normal file
View File

@ -0,0 +1,130 @@
import datetime
import requests
import json
import sys
import os
def log_test_result():
"""Reads the run_results.json file and returns a dictionary of targeted test results"""
filepath = "target/run_results.json"
with open(filepath) as f:
run = json.load(f)
logs = []
messages = {
"fail": [],
"warn": []
}
test_count = 0
warn_count = 0
fail_count = 0
for test in run["results"]:
test_count += 1
if test["status"] != "pass":
logs.append(test)
message = f"{test['failures']} record failure(s) in {test['unique_id']}"
if test["status"] == "warn":
messages["warn"].append(message)
warn_count += 1
elif test["status"] == "fail":
messages["fail"].append(message)
fail_count += 1
dbt_test_result = {
"logs": logs,
"messages": messages,
"test_count": test_count,
"warn_count": warn_count,
"fail_count": fail_count,
"elapsed_time": str(datetime.timedelta(seconds=run["elapsed_time"]))
}
return dbt_test_result
def create_message(**kwargs):
messageBody = {
"text": f"Hey{' <!here>' if len(kwargs['messages']['fail']) > 0 else ''}, new DBT test results for :{os.environ.get('DATABASE').split('_DEV')[0]}: {os.environ.get('DATABASE')}",
"attachments": [
{
"color": kwargs["color"],
"fields": [
{
"title": "Total Tests Run",
"value": kwargs["test_count"],
"short": True
},
{
"title": "Total Time Elapsed",
"value": kwargs["elapsed_time"],
"short": True
},
{
"title": "Number of Unsuccessful Tests",
"value": f"Fail: {kwargs['fail_count']}, Warn: {kwargs['warn_count']}",
"short": True
},
{
"title": "Failed Tests:",
"value": "\n".join(kwargs["messages"]["fail"]) if len(kwargs["messages"]["fail"]) > 0 else "None :)",
"short": False
}
],
"actions": [
{
"type": "button",
"text": "View Warnings",
"style": "primary",
"url": "https://github.com/FlipsideCrypto/movement-models/actions",
"confirm": {
"title": f"{kwargs['warn_count']} Warnings",
"text": "\n".join(kwargs["messages"]["warn"]) if len(kwargs["messages"]["warn"]) > 0 else "None :)",
"ok_text": "Continue to GHA",
"dismiss_text": "Dismiss"
}
}
]
}
]
}
return messageBody
def send_alert(webhook_url):
"""Sends a message to a slack channel"""
url = webhook_url
data = log_test_result()
send_message = create_message(
fail_count=data["fail_count"],
warn_count=data["warn_count"],
test_count=data["test_count"],
messages=data["messages"],
elapsed_time=data["elapsed_time"],
color="#f44336" if data["fail_count"] > 0 else "#4CAF50"
)
x = requests.post(url, json=send_message)
# test config to continue on error in workflow, so we want to exit with a non-zero code if there are any failures
if data['fail_count'] > 0:
sys.exit(1)
if __name__ == '__main__':
webhook_url = os.environ.get("SLACK_WEBHOOK_URL")
data = log_test_result()
# Only send an alert if there are failures
if data['fail_count'] > 0:
send_alert(webhook_url)