Skip to content

Commit

Permalink
Merge pull request #558 from GSA/feature/21-verify-gspc-course-requie…
Browse files Browse the repository at this point in the history
…rments

Fixed home links and added more tests.
  • Loading branch information
john-labbate authored May 13, 2024
2 parents c5c878c + fc85ad0 commit b43c372
Show file tree
Hide file tree
Showing 7 changed files with 231 additions and 8 deletions.
2 changes: 1 addition & 1 deletion training-front-end/src/components/GspcQuestions.vue
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@
To earn your GSA SmartPay Program Certification you will need to:

<ul>
<li>Complete a minimum of seven classes, including two GSA-qualifying classes and five Bank/brand-qualifying classes, during the annual GSA SmartPay Training forum.</li>
<li>Complete a minimum of seven classes, including two GSA-qualifying classes and five Bank/brand-qualifying classes, during the annual GSA SmartPay Training Forum.</li>
<li>Have a minimum of six months of continuous, hands-on experience working with the GSA SmartPay program.</li>
</ul>

Expand Down
8 changes: 5 additions & 3 deletions training-front-end/src/components/GspcRegistration.vue
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import GspcQuestions from './GspcQuestions.vue';
import FileDownLoad from "./icons/FileDownload.vue"
onErrorCaptured((err) => {
setError(err)
return false
Expand Down Expand Up @@ -36,7 +37,8 @@
})
const user = useStore(profile)
const base_url = import.meta.env.PUBLIC_API_BASE_URL
const base_api_url = import.meta.env.PUBLIC_API_BASE_URL
const base_url = import.meta.env.BASE_URL
const certPassed = ref(props.certPassed)
const certId = ref(props.certId)
const certFailed = ref(props.certFailed)
Expand Down Expand Up @@ -77,7 +79,7 @@
}
async function submitGspcRegistration(user_answers) {
const url = `${base_url}/api/v1/gspc/submission`
const url = `${base_api_url}/api/v1/gspc/submission`
quizSubmitted.value = true
let res
Expand Down Expand Up @@ -159,7 +161,7 @@
<h2>Congratulations You Earned Your GSA SmartPay Program Certificate (GSPC)</h2>
<p>You have met the requirements to earn a GSA SmartPay Program Certificate (GSPC). Your certificate has been emailed to you. Or, you may download your certificate below.</p>
<form
:action="`${base_url}/api/v1/certificate/gspc/${certId}`"
:action="`${base_api_url}/api/v1/certificate/gspc/${certId}`"
method="post"
>
<input
Expand Down
5 changes: 2 additions & 3 deletions training/services/gspc.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import logging
from training.repositories import GspcCompletionRepository, UserRepository, CertificateRepository
from training.repositories import GspcCompletionRepository, UserRepository
from training.schemas import GspcSubmission, GspcResult, GspcCompletion
from sqlalchemy.orm import Session
from training.services import Certificate
Expand All @@ -15,7 +15,7 @@
<p>
Congratulations!
</p>
<p>You've successfully met the GSPC experience requirement.</p>
<p>You've successfully met the GSPC requirements.</p>
<p>Your certificate is attached below.</p>
<p>
If you did not submit this request, you may be receiving this message in error. Please disregard this email. If you have any questions or need further
Expand All @@ -29,7 +29,6 @@ class GspcService():
def __init__(self, db: Session):
self.gspc_completion_repo = GspcCompletionRepository(db)
self.user_repo = UserRepository(db)
self.certificate_repo = CertificateRepository(db)
self.certificate_service = Certificate()

def grade(self, user_id: int, submission: GspcSubmission) -> GspcResult:
Expand Down
43 changes: 42 additions & 1 deletion training/tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,14 @@
from sqlalchemy.orm import sessionmaker, Session
from sqlalchemy import event
from training.repositories import AgencyRepository, UserRepository, QuizRepository, QuizCompletionRepository, CertificateRepository, RoleRepository
from training.schemas import AgencyCreate, RoleCreate, UserCertificate
from training.schemas import AgencyCreate, RoleCreate, UserCertificate, GspcCertificate
from training.services import QuizService
from training.config import settings
from . import factories
from training.main import app

quiz_submission_adapter = TypeAdapter(schemas.QuizSubmission)
gspc_submission_adapter = TypeAdapter(schemas.GspcSubmission)


@pytest.fixture
Expand Down Expand Up @@ -353,8 +354,48 @@ def valid_user_certificate() -> Generator[schemas.UserCertificate, None, None]:
yield UserCertificate.model_validate(testdata)


@pytest.fixture
def valid_gspc_certificate() -> Generator[schemas.GspcCertificate, None, None]:
'''
Provides a GspcCertificate schema object.
'''
testdata = {
'user_id': 2,
'user_name': "Molly",
'agency': 'Freeman Journal',
'certification_expiration_date': '2099-01-01',
'completion_date': '2023-08-21T22:59:36'
}
yield GspcCertificate.model_validate(testdata)


@pytest.fixture
def valid_gspc_passing_submission(testdata: dict) -> Generator[schemas.GspcSubmission, None, None]:
'''
Provides a GspcSubmission schema object containing valid passing responses.
'''
jsondata = testdata["gspc_submission"]["valid_passing"]
yield gspc_submission_adapter.validate_python(jsondata)


@pytest.fixture
def valid_gspc_failing_submission(testdata: dict) -> Generator[schemas.GspcSubmission, None, None]:
'''
Provides a GspcSubmission schema object containing invalid passing responses.
'''
jsondata = testdata["gspc_submission"]["valid_failing"]
yield gspc_submission_adapter.validate_python(jsondata)


@pytest.fixture
def smtp_instance():
with patch('training.services.quiz.SMTP') as smtp_mock:
with smtp_mock() as smtp:
yield smtp


@pytest.fixture
def gspc_smtp_instance():
with patch('training.services.gspc.SMTP') as smtp_mock:
with smtp_mock() as smtp:
yield smtp
4 changes: 4 additions & 0 deletions training/tests/factories.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,3 +40,7 @@ class AgencyCreateSchemaFactory(ModelFactory[schemas.AgencyCreate]):

class RoleSchemaFactory(ModelFactory[schemas.Role]):
__model__ = schemas.Role


class GspcCompletionFactory(ModelFactory[schemas.GspcCompletion]):
__model__ = schemas.GspcCompletion
147 changes: 147 additions & 0 deletions training/tests/test_gspc_service.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
import pytest
from unittest.mock import MagicMock, patch
from training import models, schemas
from training.errors import SendEmailError
from training.services import GspcService
from training.repositories import CertificateRepository, GspcCompletionRepository
from sqlalchemy.orm import Session
from .factories import GspcCompletionFactory
from datetime import datetime
from unittest.mock import ANY

from ..api import email


@patch.object(GspcCompletionRepository, "create")
@patch.object(GspcService, "email_certificate")
def test_grade_passing(
mock_gspc_service_email_certificate: MagicMock,
mock_gspc_completion_repo_create: MagicMock,
db_with_data: Session,
valid_gspc_passing_submission: schemas.GspcCompletion,
valid_user_ids,
):
gspc_service = GspcService(db_with_data)

user_id = valid_user_ids[-1]

date = datetime.now()
gspc_completion = models.GspcCompletion(
id=1,
user_id=user_id,
passed=True,
certification_expiration_date=date.replace(year=date.year + 100),
responses='',
submit_ts=date
)

mock_gspc_completion_repo_create.return_value = gspc_completion

mock_gspc_service_email_certificate.return_value = None

result = gspc_service.grade(user_id, submission=valid_gspc_passing_submission)

mock_gspc_service_email_certificate.assert_called_once_with(
"Test Three",
"[email protected]",
ANY
)

assert isinstance(result, schemas.GspcResult)
assert result.passed
assert result.cert_id == 1


@patch.object(GspcCompletionRepository, "create")
def test_grade_failing(
mock_gspc_completion_repo_create: MagicMock,
db_with_data: Session,
valid_gspc_failing_submission: schemas.GspcCompletion,
valid_user_ids,
):
gspc_service = GspcService(db_with_data)

user_id = valid_user_ids[-1]

date = datetime.now()
gspc_completion = models.GspcCompletion(
id=1,
user_id=user_id,
passed=False,
certification_expiration_date=date.replace(year=date.year + 100),
responses='',
submit_ts=date
)

mock_gspc_completion_repo_create.return_value = gspc_completion

result = gspc_service.grade(user_id, submission=valid_gspc_failing_submission)

assert isinstance(result, schemas.GspcResult)
assert not result.passed
assert result.cert_id == 1


@patch.object(GspcCompletionRepository, "create")
@patch.object(CertificateRepository, "get_certificate_by_id")
@patch.object(GspcService, "email_certificate")
def test_grade_email_certificate_error(
mock_gspc_service_email_certificate: MagicMock,
mock_certificate_repo_get_certificate_by_id: MagicMock,
mock_gspc_completion_repo_create: MagicMock,
db_with_data: Session,
valid_gspc_passing_submission: schemas.GspcSubmission,
valid_gspc_certificate: schemas.GspcCertificate,
valid_user_ids
):
gspc_service = GspcService(db_with_data)

user_id = valid_user_ids[-1]

mock_gspc_completion_repo_create.return_value = GspcCompletionFactory.build()

mock_certificate_repo_get_certificate_by_id.return_value = valid_gspc_certificate
mock_gspc_service_email_certificate.side_effect = SendEmailError

with pytest.raises(Exception):
gspc_service.grade(user_id, submission=valid_gspc_passing_submission)


@patch.multiple(email.settings,
SMTP_SERVER='email.example.com',
SMTP_PORT=999,
EMAIL_FROM='[email protected]',
EMAIL_FROM_NAME='Joseph Patrick Nannetti',
SMTP_USER='Aeolus',
SMTP_PASSWORD='cycl0ps'
)
def test_email_certificate_config(
db_with_data: Session,
gspc_smtp_instance
):
gspc_service = GspcService(db_with_data)
gspc_service.email_certificate('Test_User', '[email protected]', b'')
gspc_smtp_instance.starttls.assert_called()
gspc_smtp_instance.login.assert_called_once_with(user='Aeolus', password='cycl0ps')


def test_email_certificate_passing(
db_with_data: Session,
gspc_smtp_instance
):
gspc_service = GspcService(db_with_data)
gspc_service.email_certificate('Test_User', '[email protected]', b'')
args, _ = gspc_smtp_instance.send_message.call_args
email_message = args[0]
assert email_message['Subject'] == 'Certificate – GSA SmartPay® Program Certificate'
assert email_message['To'] == '[email protected]'


def test_email_certificate_raises_exception(
db_with_data: Session,
gspc_smtp_instance
):
gspc_service = GspcService(db_with_data)
gspc_smtp_instance.send_message.side_effect = ValueError('something went wrong')
with pytest.raises(SendEmailError):
gspc_service.email_certificate('Test_User', '[email protected]', b'')
30 changes: 30 additions & 0 deletions training/tests/testdata.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -94,3 +94,33 @@ quiz_submissions:
responses:
- question_id: 0
response_ids: [0]

gspc_submission:
valid_passing:
expiration_date: '2099-01-01'
responses:
responses:
- question_id: 0
question: 'question1'
response_id: 0
response: 'Yes'
correct: True
- question_id: 1
question: 'question2'
response_id: 0
response: 'Yes'
correct: True
valid_failing:
expiration_date: '2099-01-01'
responses:
responses:
- question_id: 0
question: 'question1'
response_id: 1
response: 'No'
correct: False
- question_id: 1
question: 'question2'
response_id: 0
response: 'Yes'
correct: True

0 comments on commit b43c372

Please sign in to comment.