Skip to content

Commit fb815d0

Browse files
feat: Added private key and passphrase authentication for snowflake connection
1 parent 1e0822d commit fb815d0

2 files changed

Lines changed: 16 additions & 36 deletions

File tree

enterprise_data/api/v1/views/lpr_data_source_snowflake.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
-------
66
SnowflakeCourseProgressSource
77
Fetches COURSE_PROGRESS per ``(enterprise_customer_uuid, user_email, courserun_key)``
8-
from ``LEARNER_PROGRESS_REPORT_INTERNAL``. Results are cached per (enterprise, pair).
8+
from ``LEARNER_PROGRESS_REPORT_EXTERNAL``. Results are cached per (enterprise, pair).
99
1010
SnowflakeCoursePassingGradeSource
1111
Fetches LOWEST_PASSING_GRADE per ``courserun_key`` from
@@ -27,7 +27,7 @@
2727
LPR_SNOWFLAKE_ROLE = None
2828
LPR_SNOWFLAKE_DATABASE = 'PROD'
2929
LPR_SNOWFLAKE_SCHEMA = 'ENTERPRISE'
30-
LPR_SNOWFLAKE_INTERNAL_TABLE = 'LEARNER_PROGRESS_REPORT_INTERNAL'
30+
LPR_SNOWFLAKE_INTERNAL_TABLE = 'LEARNER_PROGRESS_REPORT_EXTERNAL'
3131
LPR_COURSE_PROGRESS_CACHE_TIMEOUT = 300 (5 min)
3232
LPR_SNOWFLAKE_LMS_DATABASE = 'PROD'
3333
LPR_SNOWFLAKE_LMS_SCHEMA = 'LMS'
@@ -180,7 +180,7 @@ class SnowflakeCourseProgressSource(SnowflakeLPRBaseSource):
180180
"""
181181
Course progress data source backed by Snowflake.
182182
183-
Fetches COURSE_PROGRESS from ``LEARNER_PROGRESS_REPORT_INTERNAL`` filtered
183+
Fetches COURSE_PROGRESS from ``LEARNER_PROGRESS_REPORT_EXTERNAL`` filtered
184184
by both ``enterprise_customer_uuid`` and the exact ``(user_email,
185185
courserun_key)`` pairs requested. Results are cached per pair so that
186186
repeated page requests never re-query Snowflake for already-seen rows.
@@ -195,7 +195,7 @@ def _internal_table(cls):
195195
"""Return the fully-qualified Snowflake table name for internal LPR data."""
196196
database = cls._get_setting('LPR_SNOWFLAKE_DATABASE', 'PROD')
197197
schema = cls._get_setting('LPR_SNOWFLAKE_SCHEMA', 'ENTERPRISE')
198-
table = cls._get_setting('LPR_SNOWFLAKE_INTERNAL_TABLE', 'LEARNER_PROGRESS_REPORT_INTERNAL')
198+
table = cls._get_setting('LPR_SNOWFLAKE_INTERNAL_TABLE', 'LEARNER_PROGRESS_REPORT_EXTERNAL')
199199
return f'{database}.{schema}.{table}'
200200

201201
@classmethod
@@ -406,7 +406,7 @@ def _fetch_progress_for_pairs(self, enterprise_customer_uuid, pairs):
406406
if not rows:
407407
LOGGER.warning(
408408
'[course_progress] Snowflake returned 0 rows for enterprise=%s pairs=%d (batch %d). '
409-
'Verify LEARNER_PROGRESS_REPORT_INTERNAL contains data for this enterprise.',
409+
'Verify LEARNER_PROGRESS_REPORT_EXTERNAL contains data for this enterprise.',
410410
enterprise_customer_uuid, len(batch), batch_start,
411411
)
412412
else:

enterprise_data/tests/lpr/test_lpr_data_source_snowflake.py

Lines changed: 11 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -2,18 +2,21 @@
22

33
# pylint: disable=protected-access
44

5+
from types import SimpleNamespace
56
from unittest.mock import MagicMock, patch
67

78
import pytest
89

910
from django.test import override_settings
1011

12+
import enterprise_data.api.v1.views.lpr_data_source_snowflake as _lpr_module
1113
from enterprise_data.api.v1.views.lpr_data_source_snowflake import (
1214
DEFAULT_COURSE_PASSING_GRADE_CACHE_TIMEOUT,
1315
DEFAULT_COURSE_PASSING_GRADE_NEGATIVE_CACHE_TIMEOUT,
1416
DEFAULT_COURSE_PROGRESS_CACHE_TIMEOUT,
1517
SnowflakeCoursePassingGradeSource,
1618
SnowflakeCourseProgressSource,
19+
_get_snowflake_connection,
1720
)
1821
from enterprise_data.cache import get_key
1922

@@ -24,7 +27,7 @@
2427
ENTERPRISE_UUID = "a1b2c3d4-e5f6-7890-abcd-ef1234567890"
2528
NORMALIZED_UUID = "a1b2c3d4e5f67890abcdef1234567890"
2629

27-
DEFAULT_TABLE = "PROD.ENTERPRISE.LEARNER_PROGRESS_REPORT_INTERNAL"
30+
DEFAULT_TABLE = "PROD.ENTERPRISE.LEARNER_PROGRESS_REPORT_EXTERNAL"
2831
DEFAULT_OVERVIEWS_TABLE = "PROD.LMS.COURSE_OVERVIEWS_COURSEOVERVIEW"
2932

3033

@@ -669,24 +672,21 @@ def cache_side_effect(cache_key):
669672
class TestGetSnowflakeConnection:
670673
"""Tests for the module-level private-key connection factory."""
671674

675+
# _mock_connector is set in the autouse fixture (pytest fixture pattern).
676+
# pylint: disable=attribute-defined-outside-init
677+
672678
@pytest.fixture(autouse=True)
673679
def _patch_crypto_and_connector(self, monkeypatch):
674680
"""Patch crypto libs and Snowflake connector so the real function can be called."""
675-
from types import SimpleNamespace
676-
677-
import enterprise_data.api.v1.views.lpr_data_source_snowflake as mod
678-
679681
self._mock_connector = MagicMock()
680-
monkeypatch.setattr(
681-
mod, "_snowflake", SimpleNamespace(connector=self._mock_connector)
682-
)
682+
monkeypatch.setattr(_lpr_module, "_snowflake", SimpleNamespace(connector=self._mock_connector))
683683

684684
mock_key = MagicMock()
685685
mock_key.private_bytes.return_value = b"DER_BYTES"
686686
mock_ser = MagicMock()
687687
mock_ser.load_pem_private_key.return_value = mock_key
688-
monkeypatch.setattr(mod, "_serialization", mock_ser)
689-
monkeypatch.setattr(mod, "_default_backend", MagicMock(return_value="backend"))
688+
monkeypatch.setattr(_lpr_module, "_serialization", mock_ser)
689+
monkeypatch.setattr(_lpr_module, "_default_backend", MagicMock(return_value="backend"))
690690

691691
@pytest.mark.parametrize(
692692
"overrides,missing_field",
@@ -698,10 +698,6 @@ def _patch_crypto_and_connector(self, monkeypatch):
698698
)
699699
def test_raises_on_missing_credentials(self, overrides, missing_field):
700700
"""ValueError is raised when any required credential setting is absent."""
701-
from django.test import override_settings
702-
703-
from enterprise_data.api.v1.views.lpr_data_source_snowflake import _get_snowflake_connection
704-
705701
base = {
706702
"SNOWFLAKE_SERVICE_USER": "svc",
707703
"SNOWFLAKE_SERVICE_PRIVKEY": "pem",
@@ -714,13 +710,9 @@ def test_raises_on_missing_credentials(self, overrides, missing_field):
714710

715711
def test_loads_pem_key_and_connects(self):
716712
"""Happy path: loads PEM key, converts to DER, calls connector.connect."""
717-
from django.test import override_settings
718-
719-
from enterprise_data.api.v1.views.lpr_data_source_snowflake import _get_snowflake_connection
720-
721713
with override_settings(
722714
SNOWFLAKE_SERVICE_USER="svc_user",
723-
SNOWFLAKE_SERVICE_PRIVKEY="-----BEGIN ENCRYPTED PRIVATE KEY-----\nfake\n-----END ENCRYPTED PRIVATE KEY-----",
715+
SNOWFLAKE_SERVICE_PRIVKEY="-----BEGIN ENCRYPTED PRIVATE KEY-----\nfake",
724716
SNOWFLAKE_SERVICE_PASSPHRASE="s3cr3t",
725717
SNOWFLAKE_ACCOUNT="myaccount",
726718
SNOWFLAKE_ROLE="MY_ROLE",
@@ -735,10 +727,6 @@ def test_loads_pem_key_and_connects(self):
735727

736728
def test_warehouse_passed_when_provided(self):
737729
"""warehouse kwarg is forwarded to connector.connect when non-None."""
738-
from django.test import override_settings
739-
740-
from enterprise_data.api.v1.views.lpr_data_source_snowflake import _get_snowflake_connection
741-
742730
with override_settings(
743731
SNOWFLAKE_SERVICE_USER="u",
744732
SNOWFLAKE_SERVICE_PRIVKEY="pem",
@@ -750,10 +738,6 @@ def test_warehouse_passed_when_provided(self):
750738

751739
def test_warehouse_omitted_when_none(self):
752740
"""No ``warehouse`` key in connector.connect kwargs when warehouse=None."""
753-
from django.test import override_settings
754-
755-
from enterprise_data.api.v1.views.lpr_data_source_snowflake import _get_snowflake_connection
756-
757741
with override_settings(
758742
SNOWFLAKE_SERVICE_USER="u",
759743
SNOWFLAKE_SERVICE_PRIVKEY="pem",
@@ -765,10 +749,6 @@ def test_warehouse_omitted_when_none(self):
765749

766750
def test_role_override_forwarded(self):
767751
"""Explicit role kwarg overrides the settings default."""
768-
from django.test import override_settings
769-
770-
from enterprise_data.api.v1.views.lpr_data_source_snowflake import _get_snowflake_connection
771-
772752
with override_settings(
773753
SNOWFLAKE_SERVICE_USER="u",
774754
SNOWFLAKE_SERVICE_PRIVKEY="pem",

0 commit comments

Comments
 (0)