Snowflake

Analytic data warehouse provided as Software-as-a-Service.

Database · Snowflake

Details

IDSnowflake
ProviderSnowflake
CategoryDatabase
From Version5.0.0
Docker Imagedemisto/snowflake:1.0.0.10238860
Supported ModulesAgentix XSIAM

README

Use the Snowflake integration to query and update your Snowflake database.

Configure Snowflake on Cortex XSOAR

Several parameters are explained in greater detail in the Detailed Instructions section.

  1. Navigate to Settings > Integrations > Servers & Services.
  2. Search for Snowflake.
  3. Click Add instance to create and configure a new integration instance.
    • Name: a textual name for the integration instance.
    • Username
    • Account - See Detailed Description section.
    • Region (only if you are not US West)
    • Authenticator - See Detailed Description section.
    • Default warehouse to use
    • Default database to use
    • Default schema to use
    • Default role to use
    • Use system proxy settings
    • Trust server certificate (insecure)
    • Client ID
    • Client Secret
    • OAuth Token URL
    • OAuth Scope
    • Fetch incidents
    • Fetch query to retrieve new incidents. This field is mandatory when ‘Fetches incidents’ is set to true.
    • First fetch timestamp ( , e.g., 12 hours, 7 days)
    • The name of the field/column that contains the datetime object or timestamp for the data being fetched (case sensitive). This field is mandatory when ‘Fetches incidents’ is set to true.
    • The name of the field/column in the fetched data from which the name for the Cortex XSOAR incident will be assigned (case sensitive)
    • The maximum number of rows to be returned by a fetch
    • Incident type
  4. Click Test to validate the URLs, token, and connection.

Detailed Instructions

Additional information for configuring the integration instance.

Integration Parameters

  • Account
    The name of the Snowflake account to connect to without the domain name: snowflakecomputing.com. For example, mycompany.snowflakecomputing.com, enter “mycompany”. For more information, see the Snowflake Computing documentation.

  • Authenticator
    (Optional) Use this parameter to log in to your Snowflake account using Okta. For the ‘Username’ parameter, enter your ‘<okta_login_name>’. For the ‘Password’ parameter, enter your ‘<okta_password>’. The value entered here should be ‘https://<okta_account_name>.okta.com/’ where all the values between the less than and greater than symbols are replaced with the actual information specific to your Okta account.

  • Credentials
    To use Key Pair authentication, follow these instructions:

    1. Follow steps 1-4 in the instructions detailed in the Snowflake Computing documentation.
    2. Follow the instructions under the section titled Configure Cortex XSOAR Credentials at this link.
    3. Use the credentials you configured. Refer to the two images at the bottom of the section titled Configure an External Credentials Vault.
  • Authentication via External OAuth
    To configure External OAuth authentication, please consult the following setup guidelines: Snowflake External OAuth Overview.

    When using External OAuth, fill in the OAuth Client ID, OAuth Client Secret, OAuth Token URL, and optionally the OAuth Scope parameters. The Username field should still be set to the Snowflake service user that the IdP token maps to. The Password field can be left empty.

    Prerequisites

    1. In the IdP (e.g., Okta):
      • Create an API Services application (no user redirect, machine-to-machine).
      • Note the Client ID and Client Secret.
      • Create / use a Custom Authorization Server.
      • Add a custom scope named session:role:<SNOWFLAKE_ROLE> (e.g., session:role:ANALYST).
    2. In Snowflake:
      • Create an External OAuth Security Integration that trusts the IdP's issuer & JWKS URL and maps the JWT sub claim to a Snowflake user.
      • Create / use a service user of TYPE = SERVICE.

Commands

You can execute these commands from the Cortex XSOAR CLI, as part of an automation, or in a playbook. After you successfully execute a command, a DBot message appears in the War Room with the command details.

1. Run a query in Snowflake


Executes a SELECT query and retrieve the data.

Base Command

snowflake-query

Input
Argument Name Description Required
query The query to execute. Required
warehouse The warehouse to use for the query. If not specified, the default will be used. Optional
database The database to use for the query. If not specified, the default will be used. Optional
schema The schema to use for the query. If not specified, the default will be used. Optional
role The role to use for the query. If not specified, the default will be used. Optional
limit The number of rows to retrieve. Optional
columns A CSV list of columns to display in the specified order, for example: “Name, ID, Timestamp” Optional

 

Context Output
Path Type Description
Snowflake.Query String The query used to fetch results from the database.
Snowflake.Result Unknown Results from querying the database.
Snowflake.Database String The name of the database object.
Snowflake.Schema String The name of the schema object.

 

Command Example
snowflake-query warehouse=demo_wh database=demo_db schema=public query="select * from test"
Context Example
{
    "Snowflake": {
        "Query": "select * from test",
        "Schema": "public",
        "Result": [
            {
                "TS": "2018-09-11 00:00:00.000000",
                "ID": 1,
                "NAME": "b"
            },
            {
                "TS": "2018-10-12 00:00:00.000000",
                "ID": 2,
                "NAME": "kuku"
            },
            {
                "TS": "2018-10-12 00:00:00.000000",
                "ID": 3,
                "NAME": "kiki"
            },
            {
                "TS": "2018-10-12 00:00:00.000000",
                "ID": 4,
                "NAME": "kaka"
            },
            {
                "TS": "2018-10-12 00:00:00.000000",
                "ID": 5,
                "NAME": "kuku"
            },
            {
                "TS": "2019-03-26 11:14:18.574000",
                "ID": 8,
                "NAME": "blah"
            },
            {
                "TS": "2019-03-26 11:16:16.773000",
                "ID": 8,
                "NAME": "new"
            },
            {
                "TS": "2019-03-26 11:30:42.479000",
                "ID": 9,
                "NAME": "nBw4QhFcGJ"
            },
            {
                "TS": "2019-03-14 00:00:00.000000",
                "ID": 10,
                "NAME": "UPDATing"
            },
            {
                "TS": "2019-03-19 00:00:00.000000",
                "ID": 11,
                "NAME": "TESTING IT OUT again"
            },
            {
                "TS": "2019-03-28 05:32:13.355000",
                "ID": 13,
                "NAME": "New Alert"
            },
            {
                "TS": "2019-03-28 06:09:26.153000",
                "ID": 14,
                "NAME": "SHOULD FETCH THIS NEW"
            },
            {
                "TS": "2019-03-28 08:46:50.311000",
                "ID": 15,
                "NAME": "Perth"
            },
            {
                "TS": "2019-03-28 06:19:06.271000",
                "ID": 16,
                "NAME": "Edinburgh"
            },
            {
                "TS": "2019-03-28 06:19:14.059000",
                "ID": 17,
                "NAME": "York"
            },
            {
                "TS": "2019-03-28 06:20:27.126000",
                "ID": 18,
                "NAME": "Persimmon"
            },
            {
                "TS": "2019-03-28 06:28:31.001000",
                "ID": 19,
                "NAME": "Langdon"
            },
            {
                "TS": "2019-03-28 11:53:41.416000",
                "ID": 20,
                "NAME": "London"
            }
        ],
        "Database": "demo_db"
    }
}
Human Readable Output

select * from test

ID NAME TS
1 b 2018-09-11 00:00:00.000000
2 kuku 2018-10-12 00:00:00.000000
3 kiki 2018-10-12 00:00:00.000000
4 kaka 2018-10-12 00:00:00.000000
5 kuku 2018-10-12 00:00:00.000000
8 blah 2019-03-26 11:14:18.574000
8 new 2019-03-26 11:16:16.773000
9 nBw4QhFcGJ 2019-03-26 11:30:42.479000
10 UPDATing 2019-03-14 00:00:00.000000
11 TESTING IT OUT again 2019-03-19 00:00:00.000000
13 New Alert 2019-03-28 05:32:13.355000
14 SHOULD FETCH THIS NEW 2019-03-28 06:09:26.153000
15 Perth 2019-03-28 08:46:50.311000
16 Edinburgh 2019-03-28 06:19:06.271000
17 York 2019-03-28 06:19:14.059000
18 Persimmon 2019-03-28 06:20:27.126000
19 Langdon 2019-03-28 06:28:31.001000
20 London 2019-03-28 11:53:41.416000

 

2. Make a DML change in the database


Makes a DML change in the database.

Base Command

snowflake-update

Input
Argument Name Description Required
db_operation The command to execute. Required
warehouse The warehouse to use for the query. If not specified, the default will be used. Optional
database The database to use for the query. If not specified, the default will be used. Optional
schema The schema to use for the query. If not specified, the default will be used. Optional
role The role to use for the query. If not specified, the default will be used. Optional

 

Context Output

There is no context output for this command.

Command Example
snowflake-update warehouse=demo_wh database=demo_db schema=public db_operation="update test set NAME='Persimmon' where ID=18"
Human Readable Output

Operation executed successfully.

Configuration parameters

  • account — Account - See Detailed Description section. (required)
  • credentials — Username
  • region — Region (only if you are not US West)
  • authenticator — Authenticator - See Detailed Description section.
  • warehouse — Default warehouse to use (required)
  • database — Default database to use (required)
  • schema — Default schema to use
  • role — Default role to use
  • proxy — Use system proxy settings
  • insecure — Trust any certificate (not secure)
  • oauth_client_id — Client ID
  • oauth_client_secret
  • oauth_token_url — OAuth Token URL
  • oauth_scope — OAuth Scope
  • isFetch — Fetch incidents
  • fetch_query — Fetch query to retrieve new incidents. This field is mandatory when 'Fetches incidents' is set to true.
  • fetch_time — First fetch timestamp (<number> <time unit>, e.g., 12 hours, 7 days)
  • datetime_column — The name of the field/column that contains the datetime object or timestamp for the data being fetched (case sensitive). This field is mandatory when 'Fetches incidents' is set to true.
  • incident_name_column — The name of the field/column in the fetched data from which the name for the demisto incident will be assigned (case sensitive)
  • limit — The maximum number of rows to be returned by a fetch
  • incidentType — Incident type
  • incidentFetchInterval — Incidents Fetch Interval

Commands (2)

  • snowflake-query

    Executes a SELECT query and retrieve the data.

  • snowflake-update

    Makes a DML change in the database.

from datetime import datetime
from CommonServerPython import *
import pytest


@pytest.mark.parametrize(
    "time, expected_results",
    [
        (datetime(2024, 8, 14, 22, 43, 9, 851000), "2024-08-14 22:43:09.85"),
        (datetime(2024, 8, 14, 22, 43, 9), "2024-08-14 22:43:09.00"),
    ],
)
def test_convert_datetime_to_string(mocker, time, expected_results):
    """
    Given:
    - A datetime object
    - Case 1: datetime with microseconds
    - Case 2: datetime without microseconds
    When:
    - Calling convert_datetime_to_string()
    Then:
    - Ensure the datetime is converted to a string in the expected format (only 2 numbers after the decimal point)
    """
    mocker.patch.object(demisto, "params", return_value={})
    from Packs.Snowflake.Integrations.Snowflake.Snowflake import convert_datetime_to_string

    results = convert_datetime_to_string(time)
    assert results == expected_results


@pytest.mark.parametrize(
    "raw_scope, expected",
    [
        ("scope1,scope2,scope3", "scope1 scope2 scope3"),
        ("session:role:analyst,   session:role:reader", "session:role:analyst session:role:reader"),
        ("scope1, scope2 , scope3", "scope1 scope2 scope3"),
        ("", None),
        (None, None),
        (",,,", None),
        ("scope1,,scope2", "scope1 scope2"),
    ],
)
def test_parse_oauth_scope(mocker, raw_scope, expected):
    """
    Given:
    - A raw OAuth scope string (comma-separated or single)
    - Case 1: Multiple scopes comma-separated
    - Case 2: Single scope
    - Case 3: Multiple scopes with extra whitespace
    - Case 4: Empty string
    - Case 5: None value
    - Case 6: Only commas
    - Case 7: Scopes with empty entries between commas
    When:
    - Calling parse_oauth_scope()
    Then:
    - Ensure the scopes are converted to space-separated format, or None for empty input
    """
    mocker.patch.object(demisto, "params", return_value={})
    from Packs.Snowflake.Integrations.Snowflake.Snowflake import parse_oauth_scope

    result = parse_oauth_scope(raw_scope)
    assert result == expected


def test_fetch_incidents_passes_limit_key_to_snowflake_query(mocker):
    """
    Given:
    - A configured MAX_ROWS value of 1000
    When:
    - fetch_incidents() is called
    Then:
    - Ensure the args dict passed to snowflake_query contains the key "limit" (not "rows")
      with the configured MAX_ROWS value
    """
    mocker.patch.object(demisto, "params", return_value={})
    mocker.patch.object(demisto, "getLastRun", return_value={})
    mocker.patch.object(demisto, "setLastRun")
    mocker.patch.object(demisto, "incidents")

    import Packs.Snowflake.Integrations.Snowflake.Snowflake as snowflake_module

    mocker.patch.object(snowflake_module, "FETCH_TIME", "3 days")
    mocker.patch.object(snowflake_module, "FETCH_QUERY", "SELECT * FROM test")
    mocker.patch.object(snowflake_module, "DATETIME_COLUMN", "TS")
    mocker.patch.object(snowflake_module, "MAX_ROWS", 1000)

    mock_query = mocker.patch(
        "Packs.Snowflake.Integrations.Snowflake.Snowflake.snowflake_query",
        return_value=([], []),
    )

    snowflake_module.fetch_incidents()

    called_args = mock_query.call_args[0][0]
    assert "limit" in called_args, "fetch_incidents should pass 'limit' key, not 'rows'"
    assert "rows" not in called_args, "fetch_incidents should not pass 'rows' key"
    assert called_args["limit"] == 1000


def test_snowflake_query_uses_provided_limit(mocker):
    """
    Given:
    - args dict with limit set to 500
    When:
    - snowflake_query() is called
    Then:
    - Ensure fetchmany is called with the provided limit value (500)
    """
    mocker.patch.object(demisto, "params", return_value={})

    import Packs.Snowflake.Integrations.Snowflake.Snowflake as snowflake_module

    mocker.patch.object(snowflake_module, "MAX_ROWS", 10000)
    mocker.patch.object(snowflake_module, "USER", "test_user")
    mocker.patch.object(snowflake_module, "PASSWORD", "test_password")
    from Packs.Snowflake.Integrations.Snowflake.Snowflake import snowflake_query

    mock_cursor = mocker.MagicMock()
    mock_cursor.fetchmany.return_value = [{"col1": "val1"}]
    mock_cursor.description = [("col1", 2, None, None, None, None, None)]
    mock_cursor.__enter__ = mocker.MagicMock(return_value=mock_cursor)
    mock_cursor.__exit__ = mocker.MagicMock(return_value=False)

    mock_connection = mocker.MagicMock()
    mock_connection.cursor.return_value = mock_cursor
    mock_connection.__enter__ = mocker.MagicMock(return_value=mock_connection)
    mock_connection.__exit__ = mocker.MagicMock(return_value=False)

    mocker.patch("snowflake.connector.connect", return_value=mock_connection)

    args = {"limit": "500", "query": "SELECT 1"}
    snowflake_query(args)

    mock_cursor.fetchmany.assert_called_once_with(500)


def test_snowflake_query_defaults_to_100_when_no_limit(mocker):
    """
    Given:
    - args dict without a limit key
    When:
    - snowflake_query() is called
    Then:
    - Ensure fetchmany is called with the default value of 100
    """
    mocker.patch.object(demisto, "params", return_value={})

    import Packs.Snowflake.Integrations.Snowflake.Snowflake as snowflake_module

    mocker.patch.object(snowflake_module, "MAX_ROWS", 10000)
    mocker.patch.object(snowflake_module, "USER", "test_user")
    mocker.patch.object(snowflake_module, "PASSWORD", "test_password")
    from Packs.Snowflake.Integrations.Snowflake.Snowflake import snowflake_query

    mock_cursor = mocker.MagicMock()
    mock_cursor.fetchmany.return_value = [{"col1": "val1"}]
    mock_cursor.description = [("col1", 2, None, None, None, None, None)]
    mock_cursor.__enter__ = mocker.MagicMock(return_value=mock_cursor)
    mock_cursor.__exit__ = mocker.MagicMock(return_value=False)

    mock_connection = mocker.MagicMock()
    mock_connection.cursor.return_value = mock_cursor
    mock_connection.__enter__ = mocker.MagicMock(return_value=mock_connection)
    mock_connection.__exit__ = mocker.MagicMock(return_value=False)

    mocker.patch("snowflake.connector.connect", return_value=mock_connection)

    args = {"query": "SELECT 1"}
    snowflake_query(args)

    mock_cursor.fetchmany.assert_called_once_with(100)


def test_get_connection_params_raises_when_no_auth(mocker):
    """
    Given:
    - No authentication method configured (no password, certificate, or OAuth)
    When:
    - get_connection_params() is called
    Then:
    - Ensure a ValueError is raised indicating no auth method configured
    """
    mocker.patch.object(demisto, "params", return_value={})

    import Packs.Snowflake.Integrations.Snowflake.Snowflake as snowflake_module

    mocker.patch.object(snowflake_module, "PASSWORD", None)
    mocker.patch.object(snowflake_module, "CERTIFICATE", b"")
    mocker.patch.object(snowflake_module, "OAUTH_CLIENT_ID", None)
    mocker.patch.object(snowflake_module, "OAUTH_TOKEN_URL", None)
    mocker.patch.object(snowflake_module, "OAUTH_CLIENT_SECRET", None)
    mocker.patch.object(snowflake_module, "OAUTH_SCOPE", None)

    with pytest.raises(ValueError, match="No authentication method configured"):
        snowflake_module.get_connection_params({})


def test_get_connection_params_raises_when_no_user(mocker):
    """
    Given:
    - A password is configured but no username
    When:
    - get_connection_params() is called
    Then:
    - Ensure a ValueError is raised indicating username is required
    """
    mocker.patch.object(demisto, "params", return_value={})

    import Packs.Snowflake.Integrations.Snowflake.Snowflake as snowflake_module

    mocker.patch.object(snowflake_module, "PASSWORD", "test_password")
    mocker.patch.object(snowflake_module, "USER", None)

    with pytest.raises(ValueError, match="Username is required"):
        snowflake_module.get_connection_params({})


def test_get_connection_params_password_auth(mocker):
    """
    Given:
    - Username and password are configured
    When:
    - get_connection_params() is called
    Then:
    - Ensure the returned params contain the user and password
    """
    mocker.patch.object(demisto, "params", return_value={})

    import Packs.Snowflake.Integrations.Snowflake.Snowflake as snowflake_module

    mocker.patch.object(snowflake_module, "USER", "test_user")
    mocker.patch.object(snowflake_module, "PASSWORD", "test_password")
    mocker.patch.object(snowflake_module, "CERTIFICATE", b"")
    mocker.patch.object(snowflake_module, "OAUTH_CLIENT_ID", None)
    mocker.patch.object(snowflake_module, "OAUTH_TOKEN_URL", None)
    mocker.patch.object(snowflake_module, "OAUTH_CLIENT_SECRET", None)
    mocker.patch.object(snowflake_module, "OAUTH_SCOPE", None)

    params = snowflake_module.get_connection_params({})

    assert params["user"] == "test_user"
    assert params["password"] == "test_password"


def test_get_connection_params_oauth(mocker):
    """
    Given:
    - OAuth client id, secret and token url are configured
    When:
    - get_connection_params() is called
    Then:
    - Ensure the returned params use OAUTH_CLIENT_CREDENTIALS authenticator and OAuth values
    """
    mocker.patch.object(demisto, "params", return_value={})

    import Packs.Snowflake.Integrations.Snowflake.Snowflake as snowflake_module

    mocker.patch.object(snowflake_module, "USER", "test_user")
    mocker.patch.object(snowflake_module, "PASSWORD", None)
    mocker.patch.object(snowflake_module, "CERTIFICATE", b"")
    mocker.patch.object(snowflake_module, "OAUTH_CLIENT_ID", "client_id")
    mocker.patch.object(snowflake_module, "OAUTH_TOKEN_URL", "https://token.url")
    mocker.patch.object(snowflake_module, "OAUTH_CLIENT_SECRET", "client_secret")
    mocker.patch.object(snowflake_module, "OAUTH_SCOPE", "scope1 scope2")

    params = snowflake_module.get_connection_params({})

    assert params["authenticator"] == "OAUTH_CLIENT_CREDENTIALS"
    assert params["oauth_client_id"] == "client_id"
    assert params["oauth_client_secret"] == "client_secret"
    assert params["oauth_token_request_url"] == "https://token.url"
    assert params["oauth_scope"] == "scope1 scope2"


def test_get_connection_params_oauth_missing_fields(mocker):
    """
    Given:
    - OAuth client id is set but token url and secret are missing
    When:
    - get_connection_params() is called
    Then:
    - Ensure a ValueError is raised indicating all OAuth fields are required
    """
    mocker.patch.object(demisto, "params", return_value={})

    import Packs.Snowflake.Integrations.Snowflake.Snowflake as snowflake_module

    mocker.patch.object(snowflake_module, "USER", "test_user")
    mocker.patch.object(snowflake_module, "PASSWORD", None)
    mocker.patch.object(snowflake_module, "CERTIFICATE", b"")
    mocker.patch.object(snowflake_module, "OAUTH_CLIENT_ID", "client_id")
    mocker.patch.object(snowflake_module, "OAUTH_TOKEN_URL", None)
    mocker.patch.object(snowflake_module, "OAUTH_CLIENT_SECRET", None)
    mocker.patch.object(snowflake_module, "OAUTH_SCOPE", None)

    with pytest.raises(ValueError, match="OAuth Client ID, Client Secret, and Token URL"):
        snowflake_module.get_connection_params({})


def test_set_provided(mocker):
    """
    Given:
    - Various combinations of val1 and val2
    When:
    - set_provided() is called
    Then:
    - Ensure the correct value is set (val1 preferred, val2 fallback, nothing if both empty)
    """
    mocker.patch.object(demisto, "params", return_value={})

    from Packs.Snowflake.Integrations.Snowflake.Snowflake import set_provided

    params: dict = {}
    set_provided(params, "key1", "value1")
    set_provided(params, "key2", None, "fallback")
    set_provided(params, "key3", None, None)

    assert params["key1"] == "value1"
    assert params["key2"] == "fallback"
    assert "key3" not in params


def test_process_table_row(mocker):
    """
    Given:
    - A row with a Decimal value and a datetime value plus the corresponding checks
    When:
    - process_table_row() is called
    Then:
    - Ensure Decimal is converted to string and datetime is converted to a formatted string
    """
    from decimal import Decimal

    mocker.patch.object(demisto, "params", return_value={})

    from Packs.Snowflake.Integrations.Snowflake.Snowflake import process_table_row

    row = {"num": Decimal("10.5"), "ts": datetime(2024, 8, 14, 22, 43, 9), "other": "text"}
    checks = {"isDecimal": ["num"], "isDT": ["ts"]}

    result = process_table_row(row, checks)

    assert result["num"] == "10.5"
    assert result["ts"] == "2024-08-14 22:43:09.00"
    assert result["other"] == "text"


def test_format_to_json_serializable(mocker):
    """
    Given:
    - Column descriptions with number/int and timestamp type codes and a list of rows
    When:
    - format_to_json_serializable() is called
    Then:
    - Ensure the Decimal and datetime values are reformatted to json serializable types
    """
    from decimal import Decimal

    mocker.patch.object(demisto, "params", return_value={})

    from Packs.Snowflake.Integrations.Snowflake.Snowflake import format_to_json_serializable

    column_descriptions = [
        ("num", 0, None, None, None, None, None),
        ("ts", 4, None, None, None, None, None),
    ]
    results = [{"num": Decimal("3.14"), "ts": datetime(2024, 8, 14, 22, 43, 9)}]

    formatted = format_to_json_serializable(column_descriptions, results)

    assert formatted[0]["num"] == "3.14"
    assert formatted[0]["ts"] == "2024-08-14 22:43:09.00"


def test_error_message_from_snowflake_error(mocker):
    """
    Given:
    - A snowflake error-like object with errno 606
    When:
    - error_message_from_snowflake_error() is called
    Then:
    - Ensure a formatted error message that mentions specifying an active warehouse is returned
    """
    mocker.patch.object(demisto, "params", return_value={})

    from Packs.Snowflake.Integrations.Snowflake.Snowflake import error_message_from_snowflake_error

    class FakeError:
        errno = 606
        sqlstate = "00000"
        sfqid = "abc-123"
        raw_msg = "No active warehouse. Additional info."

    result = error_message_from_snowflake_error(FakeError())

    assert "Snowflake DB error code: 606" in result
    assert "Specify an active warehouse" in result


def test_row_to_incident(mocker):
    """
    Given:
    - Column descriptions and a row containing a datetime column
    When:
    - row_to_incident() is called
    Then:
    - Ensure an incident dict with name, occurred, timestamp and rawJSON is returned
    """
    mocker.patch.object(demisto, "params", return_value={})

    import Packs.Snowflake.Integrations.Snowflake.Snowflake as snowflake_module

    mocker.patch.object(snowflake_module, "DATETIME_COLUMN", "TS")
    mocker.patch.object(snowflake_module, "INCIDENT_NAME_COLUMN", "NAME")

    column_descriptions = [("TS", 4, None, None, None, None, None)]
    row = {"TS": datetime(2024, 8, 14, 22, 43, 9), "NAME": "my-incident"}

    incident = snowflake_module.row_to_incident(column_descriptions, row)

    assert incident["name"] == "my-incident"
    assert "occurred" in incident
    assert "timestamp" in incident
    assert "rawJSON" in incident