<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Forem: Vincent Davis</title>
    <description>The latest articles on Forem by Vincent Davis (@vincent_davis_7b6665836a9).</description>
    <link>https://forem.com/vincent_davis_7b6665836a9</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3924344%2Fbc1b27ec-93e3-49a8-9ec1-85628a372281.jpg</url>
      <title>Forem: Vincent Davis</title>
      <link>https://forem.com/vincent_davis_7b6665836a9</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/vincent_davis_7b6665836a9"/>
    <language>en</language>
    <item>
      <title>Building GBIM Observability From Correlation IDs to a Populated k6 Dashboard</title>
      <dc:creator>Vincent Davis</dc:creator>
      <pubDate>Tue, 12 May 2026 03:12:34 +0000</pubDate>
      <link>https://forem.com/vincent_davis_7b6665836a9/membangun-observability-gbim-dari-correlation-id-sampai-dashboard-k6-yang-terisi-3aj1</link>
      <guid>https://forem.com/vincent_davis_7b6665836a9/membangun-observability-gbim-dari-correlation-id-sampai-dashboard-k6-yang-terisi-3aj1</guid>
      <description>&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;p&gt;In this iteration, I improved the observability of GBIM on both the backend and frontend, based on the latest &lt;code&gt;origin/staging&lt;/code&gt; branch. The focus is not just on installing monitoring tools. It is about ensuring that operational data relevant to the application's workflow actually appears in Prometheus, Grafana, Sentry, GA4, and the k6 dashboard.&lt;/p&gt;

&lt;p&gt;Key changes include &lt;code&gt;gbm_*&lt;/code&gt; custom Prometheus metrics, structured logs with end-to-end correlation IDs, GA4 event analytics for user activity, Prometheus alert rules, and a k6 job utilizing Prometheus remote write. This ensures the &lt;code&gt;k6-prometheus&lt;/code&gt; dashboard is no longer empty after the job runs.&lt;/p&gt;

&lt;h2&gt;
  
  
  Initial Problems
&lt;/h2&gt;

&lt;p&gt;The monitoring stack was already in place prior to these changes, but several crucial pieces of evidence remained weak.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The k6 dashboard in Grafana was still empty because no k6 job was writing test results to the Prometheus remote write.&lt;/li&gt;
&lt;li&gt;The flows for registration, account activation, token reactivation, admin account verification, and admin submission status updates lacked explicit business metrics.&lt;/li&gt;
&lt;li&gt;Tracing from frontend requests to backend logs was inconsistent because correlation IDs were not passed from the frontend and returned by the backend.&lt;/li&gt;
&lt;li&gt;User activity events on the frontend had not been standardized to provide evidence of user activity monitoring.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Implemented Solutions
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Backend Metrics
&lt;/h3&gt;

&lt;p&gt;The backend now includes a &lt;code&gt;monitoring/metrics.py&lt;/code&gt; file containing Prometheus counters and histograms for essential flows.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;gbm_auth_register_total{role,outcome}&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;gbm_auth_activation_total{outcome}&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;gbm_auth_reactivation_total{outcome}&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;gbm_auth_email_send_duration_seconds{event,outcome}&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;gbm_admin_account_verification_total{action,outcome}&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;gbm_pengajuan_admin_status_update_total{status,outcome}&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These metrics are emitted directly from the views and services handling registration, activation, reactivation, admin account verification, and admin submission status updates. Because of this, the dashboard displays business outcomes like &lt;code&gt;success&lt;/code&gt;, &lt;code&gt;validation_error&lt;/code&gt;, &lt;code&gt;token_invalid&lt;/code&gt;, &lt;code&gt;token_expired&lt;/code&gt;, &lt;code&gt;server_error&lt;/code&gt;, and &lt;code&gt;service_error&lt;/code&gt; instead of just generic HTTP requests.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Foqbg9w647p71gyj7cnvh.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Foqbg9w647p71gyj7cnvh.png" alt=" " width="800" height="183"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fni5220t6fln40d7i8zq1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fni5220t6fln40d7i8zq1.png" alt=" " width="800" height="521"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  2. End-to-End Correlation IDs
&lt;/h3&gt;

&lt;p&gt;The frontend now attaches an &lt;code&gt;X-Correlation-ID&lt;/code&gt; header to requests via &lt;code&gt;lib/api.ts&lt;/code&gt;. The backend accepts valid correlation IDs, generates a new one if the provided header is unsafe, and then returns it in the response.&lt;/p&gt;

&lt;p&gt;The benefit is straightforward. When a user experiences an error on the frontend, the correlation ID from the response can be used to search for the relevant backend logs. This speeds up investigations since a single request can be traced across multiple layers.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmjzsyzuc6806p7i88qsy.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmjzsyzuc6806p7i88qsy.png" alt=" " width="714" height="602"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Frontend Analytics
&lt;/h3&gt;

&lt;p&gt;The frontend includes a &lt;code&gt;lib/analytics.ts&lt;/code&gt; helper to send GA4 events. This helper is a no-op when &lt;code&gt;window.gtag&lt;/code&gt; is unavailable, and more importantly, it only activates if all the following conditions are met.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;code&gt;NEXT_PUBLIC_GA_MEASUREMENT_ID&lt;/code&gt; is available.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;NEXT_PUBLIC_APP_ENV&lt;/code&gt; is set to &lt;code&gt;staging&lt;/code&gt; or &lt;code&gt;production&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;The runtime host is included in the analytics allowlist, for instance, &lt;code&gt;gbim-staging.ppl.cs.ui.ac.id&lt;/code&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Thanks to this validation, analytics events are not sent from the local environment even if a developer accidentally populates the GA4 variables in &lt;code&gt;.env.local&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Instrumented events include the following.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;register_submitted&lt;/code&gt;, &lt;code&gt;register_success&lt;/code&gt;, &lt;code&gt;register_failed&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;activation_verified&lt;/code&gt;, &lt;code&gt;activation_expired&lt;/code&gt;, &lt;code&gt;activation_used&lt;/code&gt;, &lt;code&gt;activation_invalid&lt;/code&gt;, &lt;code&gt;activation_rate_limited&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;reactivation_requested&lt;/code&gt;, &lt;code&gt;reactivation_success&lt;/code&gt;, &lt;code&gt;reactivation_failed&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;admin_verification_list_viewed&lt;/code&gt;, &lt;code&gt;admin_verification_detail_clicked&lt;/code&gt;, &lt;code&gt;admin_verification_status_updated&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;pengajuan_admin_status_updated&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;[Placeholder SS-04] GA4 Realtime or DebugView displays one of the staging events, such as &lt;code&gt;register_submitted&lt;/code&gt;, &lt;code&gt;activation_invalid&lt;/code&gt;, or &lt;code&gt;admin_verification_list_viewed&lt;/code&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  4. Alerting
&lt;/h3&gt;

&lt;p&gt;Prometheus alert rules were added for actionable conditions.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;ActivationFailureRateHigh&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;RegisterServerError&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;AdminVerificationErrorBurst&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;PengajuanStatusUpdateServiceError&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;K6HighFailureRate&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;K6HighP95Latency&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These alerts transform the monitoring setup from a passive dashboard into a proactive system. The team is immediately notified when the activation failure rate, registration errors, admin verification errors, submission service errors, or k6 metrics exceed their thresholds.&lt;/p&gt;

&lt;p&gt;Grafana alerting is also provisioned to a Discord contact point named &lt;code&gt;GBM_MONITORING_DISCORD&lt;/code&gt; using the &lt;code&gt;DISCORD_WEBHOOK_URL&lt;/code&gt; environment variable. This is critical because alerts that stop at the dashboard are insufficient as operational evidence. Active notifications must be validated directly from the team's communication channels.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;[Placeholder SS-05] Grafana Alerting shows the business and k6 rules along with the &lt;code&gt;GBM_MONITORING_DISCORD&lt;/code&gt; contact point utilizing a Discord webhook.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  5. Populating the Grafana Dashboard with k6
&lt;/h3&gt;

&lt;p&gt;The k6 component is prepared to populate the following dashboard link &lt;code&gt;[https://gbim-staging.ppl.cs.ui.ac.id/grafana/d/ccbb2351-2ae2-462f-ae0e-f2c893ad1028/k6-prometheus](https://gbim-staging.ppl.cs.ui.ac.id/grafana/d/ccbb2351-2ae2-462f-ae0e-f2c893ad1028/k6-prometheus)&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;The prepared implementation involves several key configurations.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The Prometheus deployment utilizes the &lt;code&gt;--web.enable-remote-write-receiver&lt;/code&gt; argument.&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;k8s/job/k6-monitoring-smoke.yaml&lt;/code&gt; Kubernetes Job runs the &lt;code&gt;grafana/k6:latest&lt;/code&gt; image.&lt;/li&gt;
&lt;li&gt;The k6 job includes &lt;code&gt;ttlSecondsAfterFinished: 600&lt;/code&gt; so that &lt;code&gt;Completed&lt;/code&gt; pods are cleaned up automatically.&lt;/li&gt;
&lt;li&gt;k6 leverages the &lt;code&gt;experimental-prometheus-rw&lt;/code&gt; output.&lt;/li&gt;
&lt;li&gt;The remote write is directed to &lt;code&gt;http://prometheus:9090/api/v1/write&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;The test is tagged with &lt;code&gt;testid=monitoring-smoke&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Local scripts are available at &lt;code&gt;k6/monitoring-smoke.js&lt;/code&gt; and &lt;code&gt;k6/activation-alert.js&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;The pipeline waits for the backend to be ready using &lt;code&gt;kubectl rollout status deployment/gurubesarmengajar&lt;/code&gt; and the &lt;code&gt;/api/metrics&lt;/code&gt; readiness check before executing k6.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Important note. The k6 dashboard will populate once the latest Prometheus manifest is deployed and the k6 Job is executed. If the job has never run or the Grafana time range is too narrow, the dashboard may still appear empty. For evidence purposes, use a time range that covers the job's execution time, such as &lt;code&gt;now-15m&lt;/code&gt; or &lt;code&gt;now-1h&lt;/code&gt;.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;[Placeholder SS-06] The &lt;code&gt;k6-monitoring-smoke&lt;/code&gt; pipeline job completes, the k6 logs indicate the test is running, and the output utilizes the Prometheus remote write.&lt;/p&gt;

&lt;p&gt;[Placeholder SS-07] The &lt;code&gt;k6 Prometheus&lt;/code&gt; dashboard is populated with &lt;code&gt;testid=monitoring-smoke&lt;/code&gt;, covering request rate, p95 latency, failure rate, virtual users, and checks success rate.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  How to Reproduce k6 Evidence
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Deploy the latest Prometheus manifest that enables the remote write receiver.&lt;/li&gt;
&lt;li&gt;Run the k6 Job using the following command.
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl apply &lt;span class="nt"&gt;-f&lt;/span&gt; k8s/job/k6-monitoring-smoke.yaml

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;Check the job status.
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl &lt;span class="nt"&gt;-n&lt;/span&gt; ppl-aptikom get job k6-monitoring-smoke
kubectl &lt;span class="nt"&gt;-n&lt;/span&gt; ppl-aptikom logs job/k6-monitoring-smoke

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;Open the &lt;code&gt;k6-prometheus&lt;/code&gt; dashboard, select the Prometheus data source, choose &lt;code&gt;testid=monitoring-smoke&lt;/code&gt;, and then set the time range to a period after the job execution.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;You can use these queries for a quick PromQL validation.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;k6_http_reqs_total{testid="monitoring-smoke"}
k6_http_req_duration_seconds_p95{testid="monitoring-smoke"}
k6_http_req_failed_rate{testid="monitoring-smoke"}

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If the cluster still retains older metrics without unit suffixes, the dashboard fallback also accepts &lt;code&gt;k6_http_req_duration_p95&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Mapping to CPL 6 DA
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Criterion 1 Built-in Platform Monitoring
&lt;/h3&gt;

&lt;p&gt;Prometheus and Grafana are not merely installed as isolated services. They are fully integrated into the GBIM application lifecycle within Kubernetes. The backend exposes &lt;code&gt;/api/metrics&lt;/code&gt; for Prometheus scraping and a health check endpoint for readiness validation, while the Grafana dashboard is provisioned via ConfigMap so it persists even if the Grafana pod restarts.&lt;/p&gt;

&lt;p&gt;This implementation also embeds observability directly into the deployment pipeline. The Prometheus manifests, alert rules, dashboards, and Grafana alerting configurations are applied through CI/CD, meaning monitoring changes can be reviewed and pushed just like application code. Through this pattern, monitoring becomes a reproducible part of the platform rather than a manual configuration in the Grafana UI.&lt;/p&gt;

&lt;h3&gt;
  
  
  Criterion 2 Standard Tool Setup with Live Data
&lt;/h3&gt;

&lt;p&gt;The utilized tools represent industry standards. We use Prometheus for metrics, Grafana for dashboards and alerting, k6 for load testing, GA4 for frontend analytics, Sentry for error visibility, and structured logs for backend investigations. The displayed data is entirely authentic rather than static mocks, as the metrics originate from actual staging requests, user flow events, and a k6 job that genuinely writes test results to the Prometheus remote write.&lt;/p&gt;

&lt;p&gt;The most visible improvement is on the k6 dashboard. Previously, the &lt;code&gt;k6-prometheus&lt;/code&gt; dashboard was completely blank because no job was writing k6 metrics to Prometheus. Following these changes, the pipeline executes &lt;code&gt;k6-monitoring-smoke&lt;/code&gt;, tags it with &lt;code&gt;testid=monitoring-smoke&lt;/code&gt;, and the dashboard subsequently reads metrics such as &lt;code&gt;k6_http_reqs_total&lt;/code&gt;, &lt;code&gt;k6_http_req_failed_rate&lt;/code&gt;, and &lt;code&gt;k6_http_req_duration_seconds_p95&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Criterion 3 Customization Tailored to Workflows
&lt;/h3&gt;

&lt;p&gt;Customizations are tailored specifically to the GBIM workflows that matter most for operations, going far beyond basic CPU, memory, or HTTP status tracking. The backend introduces business metrics for registration, account activation, token reactivation, admin account verification, email delivery duration, and admin submission status updates. Labels such as &lt;code&gt;outcome&lt;/code&gt;, &lt;code&gt;role&lt;/code&gt;, &lt;code&gt;action&lt;/code&gt;, and &lt;code&gt;status&lt;/code&gt; enable the dashboard to distinctly categorize successes, validation failures, invalid tokens, expired tokens, not found errors, server errors, and service errors.&lt;/p&gt;

&lt;p&gt;On the frontend, GA4 events are specifically designed to track relevant user activities, such as registration submissions, activation outcomes, reactivation requests, viewing the admin verification list, clicking account details, updating account statuses, and updating submission statuses. To prevent polluting the analytics data, the analytics wrapper is exclusively active in &lt;code&gt;staging&lt;/code&gt; or &lt;code&gt;production&lt;/code&gt; environments and on approved hosts.&lt;/p&gt;

&lt;h3&gt;
  
  
  Criterion 4 Advanced Usage
&lt;/h3&gt;

&lt;p&gt;Advanced usage is demonstrated in two main areas. These are actionable alerts and end-to-end observability. Prometheus and Grafana alerts are established for conditions requiring follow-up actions, including spikes in activation failures, registration server errors, admin verification errors, submission update service errors, k6 failure rates, and k6 p95 latency. These alerts are routed to Discord via the &lt;code&gt;GBM_MONITORING_DISCORD&lt;/code&gt; contact point, eliminating the need for the team to constantly monitor dashboards to detect issues.&lt;/p&gt;

&lt;p&gt;Furthermore, correlation IDs seamlessly link frontend requests to backend logs. When a user encounters an error in the browser, the &lt;code&gt;X-Correlation-ID&lt;/code&gt; provided in the response can be queried in the backend logs as &lt;code&gt;corr_id&lt;/code&gt;, ensuring investigations do not stall at aggregate dashboards. The combination of metrics, alerts, load tests, analytics, and correlation IDs transforms this monitoring setup into a powerful diagnostic tool rather than just passive documentation.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;These enhancements make GBIM observability highly concrete for CPL 6 DA requirements, as every layer now possesses its own operational evidence. The backend supplies custom Prometheus metrics for business flows, the frontend dispatches environment-restricted analytics events, client-server requests are traceable via correlation IDs, and k6 generates performance metrics that flow directly into the Grafana dashboard through remote write.&lt;/p&gt;

&lt;p&gt;The final outcome is not merely having Grafana installed. It is a robust monitoring system capable of answering vital operational questions. We can now determine if registrations frequently fail, if activations are problematic, if admin verifications generate errors, if submission status updates are stable, and if the backend remains responsive under k6 testing. Once the backend and frontend are deployed and the k6 job completes, the screenshots inserted in each section will serve as definitive proof that the monitoring functions flawlessly from the deployment pipeline all the way to the dashboards and alerting systems.&lt;/p&gt;

</description>
      <category>devops</category>
      <category>monitoring</category>
      <category>performance</category>
      <category>sre</category>
    </item>
    <item>
      <title>Beyond Unit Tests: Implementing BDD and Penetration Testing in GBIM</title>
      <dc:creator>Vincent Davis</dc:creator>
      <pubDate>Mon, 11 May 2026 09:43:49 +0000</pubDate>
      <link>https://forem.com/vincent_davis_7b6665836a9/beyond-unit-tests-implementing-bdd-and-penetration-testing-in-gbim-3nlc</link>
      <guid>https://forem.com/vincent_davis_7b6665836a9/beyond-unit-tests-implementing-bdd-and-penetration-testing-in-gbim-3nlc</guid>
      <description>&lt;h1&gt;
  
  
  IR Part A #4 - More Testing: Stress Testing, Penetration Testing, Security Testing, and BDD
&lt;/h1&gt;

&lt;p&gt;Prepared on: May 11, 2026&lt;br&gt;&lt;br&gt;
Project scope: &lt;code&gt;BE-GBM&lt;/code&gt; and &lt;code&gt;fe-gbm&lt;/code&gt;&lt;br&gt;&lt;br&gt;
Feature scope: account registration, account activation, login lockout, admin approval/rejection of submissions, and non-admin access control.&lt;/p&gt;
&lt;h2&gt;
  
  
  Claim Summary
&lt;/h2&gt;

&lt;p&gt;This claim demonstrates that the GBM project has implemented advanced testing in four areas required by the IR B5 rubric: security testing, penetration testing, behavior-driven development, and stress testing.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Security Testing&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Tools and approach: GitLab SAST, Dependency Scanning, Secret Detection, and OWASP ZAP baseline.&lt;/p&gt;

&lt;p&gt;Primary evidence: the FE security pipeline passed, the BE security analyzer jobs passed in MR pipeline 279225, and the ZAP baseline job is available for staging or scheduled execution.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Penetration Testing&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Tools and approach: OWASP-mapped abuse-case tests.&lt;/p&gt;

&lt;p&gt;Primary evidence: 8 concrete abuse paths cover login, registration, activation tokens, authorization, mass assignment, and email enumeration.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;BDD&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Tools and approach: Behave Django and Playwright BDD.&lt;/p&gt;

&lt;p&gt;Primary evidence: BE has 3 features, 12 scenarios, and 57 steps passed. FE has 3 Playwright BDD tests discovered, and &lt;code&gt;e2e-bdd&lt;/code&gt; passed in CI.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Stress Testing&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Tools and approach: Locust.&lt;/p&gt;

&lt;p&gt;Primary evidence: registration burst, authenticated activity, quality-assurance, and admin dashboard workloads are modeled as manual headless runs with HTML/CSV output.&lt;/p&gt;

&lt;p&gt;The strongest evidence is executable and measurable: backend BDD passes, backend regression subset passes, frontend BDD/security pipeline passes, and backend security analyzer jobs pass. Stress testing is intentionally treated as manual evidence rather than a merge-request gate because staging capacity, credentials, seed data, rate limits, and email side effects can make performance numbers non-deterministic.&lt;/p&gt;
&lt;h2&gt;
  
  
  Screenshot Evidence
&lt;/h2&gt;

&lt;p&gt;Only non-trivial evidence needs screenshots. Commit links, command snippets, and static documentation paths are already explicit in text and do not need images.&lt;/p&gt;
&lt;h3&gt;
  
  
  1. Backend MR Pipeline
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvvwq8krfmuom6qffvcz5.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvvwq8krfmuom6qffvcz5.png" alt=" " width="800" height="531"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  2. Frontend MR Pipeline
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F545wyp5ojaa84twp2uwf.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F545wyp5ojaa84twp2uwf.png" alt=" " width="800" height="462"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  3. Behave Results
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://gitlab.cs.ui.ac.id/ppl-fasilkom-ui/2026/kelas-d/group1-gb/be-gbm/-/jobs/802493" rel="noopener noreferrer"&gt;https://gitlab.cs.ui.ac.id/ppl-fasilkom-ui/2026/kelas-d/group1-gb/be-gbm/-/jobs/802493&lt;/a&gt;&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Foggwysoipgfif30hi6zp.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Foggwysoipgfif30hi6zp.png" alt=" " width="800" height="578"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  4. integrated testing with CI/CD
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhokfau7q2q7li5olgzus.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhokfau7q2q7li5olgzus.png" alt=" " width="800" height="467"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Merge Requests and Commit Links
&lt;/h2&gt;
&lt;h3&gt;
  
  
  Merge Requests
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Repo&lt;/th&gt;
&lt;th&gt;Merge Request&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Backend&lt;/td&gt;
&lt;td&gt;&lt;a href="https://gitlab.cs.ui.ac.id/ppl-fasilkom-ui/2026/kelas-d/group1-gb/be-gbm/-/merge_requests/157" rel="noopener noreferrer"&gt;https://gitlab.cs.ui.ac.id/ppl-fasilkom-ui/2026/kelas-d/group1-gb/be-gbm/-/merge_requests/157&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Frontend&lt;/td&gt;
&lt;td&gt;&lt;a href="https://gitlab.cs.ui.ac.id/ppl-fasilkom-ui/2026/kelas-d/group1-gb/fe-gbm/-/merge_requests/141" rel="noopener noreferrer"&gt;https://gitlab.cs.ui.ac.id/ppl-fasilkom-ui/2026/kelas-d/group1-gb/fe-gbm/-/merge_requests/141&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;
&lt;h3&gt;
  
  
  Backend Commits Used as Evidence
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Commit&lt;/th&gt;
&lt;th&gt;Evidence Contribution&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://gitlab.cs.ui.ac.id/ppl-fasilkom-ui/2026/kelas-d/group1-gb/be-gbm/-/commit/64046e3c70e2ad419d14fc5580863821641991a0" rel="noopener noreferrer"&gt;https://gitlab.cs.ui.ac.id/ppl-fasilkom-ui/2026/kelas-d/group1-gb/be-gbm/-/commit/64046e3c70e2ad419d14fc5580863821641991a0&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Adds the backend IR B5 testing baseline: security templates, BDD features, load-test documentation, and evidence structure.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://gitlab.cs.ui.ac.id/ppl-fasilkom-ui/2026/kelas-d/group1-gb/be-gbm/-/commit/8b75a9ed85b67ffc1e920a1bb842ca4503c4d684" rel="noopener noreferrer"&gt;https://gitlab.cs.ui.ac.id/ppl-fasilkom-ui/2026/kelas-d/group1-gb/be-gbm/-/commit/8b75a9ed85b67ffc1e920a1bb842ca4503c4d684&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Adds executable backend BDD and penetration-testing scenarios against real DRF endpoints.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://gitlab.cs.ui.ac.id/ppl-fasilkom-ui/2026/kelas-d/group1-gb/be-gbm/-/commit/f9f62adb791fd5af38cc70d93bdbb0eb6ac7c62f" rel="noopener noreferrer"&gt;https://gitlab.cs.ui.ac.id/ppl-fasilkom-ui/2026/kelas-d/group1-gb/be-gbm/-/commit/f9f62adb791fd5af38cc70d93bdbb0eb6ac7c62f&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Adds &lt;code&gt;behave&lt;/code&gt; and &lt;code&gt;behave-django&lt;/code&gt; to &lt;code&gt;requirements.txt&lt;/code&gt; so BDD can run consistently in the Django environment.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://gitlab.cs.ui.ac.id/ppl-fasilkom-ui/2026/kelas-d/group1-gb/be-gbm/-/commit/eb07e5e2a224a563605238c20fa33526a3c85e34" rel="noopener noreferrer"&gt;https://gitlab.cs.ui.ac.id/ppl-fasilkom-ui/2026/kelas-d/group1-gb/be-gbm/-/commit/eb07e5e2a224a563605238c20fa33526a3c85e34&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Enables backend security analyzer execution in the MR pipeline.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://gitlab.cs.ui.ac.id/ppl-fasilkom-ui/2026/kelas-d/group1-gb/be-gbm/-/commit/dd64fdf164e2bbfbf36860d9caf819fbccf37341" rel="noopener noreferrer"&gt;https://gitlab.cs.ui.ac.id/ppl-fasilkom-ui/2026/kelas-d/group1-gb/be-gbm/-/commit/dd64fdf164e2bbfbf36860d9caf819fbccf37341&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Finalizes backend CI and stress-testing policy: deterministic MR checks remain in CI, while stress evidence is documented through Locust manual runs.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;
&lt;h3&gt;
  
  
  Frontend Commits Used as Evidence
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Commit&lt;/th&gt;
&lt;th&gt;Evidence Contribution&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://gitlab.cs.ui.ac.id/ppl-fasilkom-ui/2026/kelas-d/group1-gb/fe-gbm/-/commit/fff3cb1fcb7556b31399781888d90e6de15bbab8" rel="noopener noreferrer"&gt;https://gitlab.cs.ui.ac.id/ppl-fasilkom-ui/2026/kelas-d/group1-gb/fe-gbm/-/commit/fff3cb1fcb7556b31399781888d90e6de15bbab8&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Adds frontend IR B5 security templates and Playwright BDD structure.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://gitlab.cs.ui.ac.id/ppl-fasilkom-ui/2026/kelas-d/group1-gb/fe-gbm/-/commit/dd62510177af54d760b7759299ee43df00282222" rel="noopener noreferrer"&gt;https://gitlab.cs.ui.ac.id/ppl-fasilkom-ui/2026/kelas-d/group1-gb/fe-gbm/-/commit/dd62510177af54d760b7759299ee43df00282222&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Implements API-backed frontend BDD flows for registration and admin submission behavior.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://gitlab.cs.ui.ac.id/ppl-fasilkom-ui/2026/kelas-d/group1-gb/fe-gbm/-/commit/1e5beadbca00a422b05922935af6248ce25ea874" rel="noopener noreferrer"&gt;https://gitlab.cs.ui.ac.id/ppl-fasilkom-ui/2026/kelas-d/group1-gb/fe-gbm/-/commit/1e5beadbca00a422b05922935af6248ce25ea874&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Enables frontend MR pipeline execution for security analyzers, BDD generation/discovery, and quality checks.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;
&lt;h2&gt;
  
  
  Execution Evidence
&lt;/h2&gt;
&lt;h3&gt;
  
  
  Backend Evidence
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Check&lt;/th&gt;
&lt;th&gt;Command or Source&lt;/th&gt;
&lt;th&gt;Result&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Dependency installation&lt;/td&gt;
&lt;td&gt;&lt;code&gt;.\.venv\Scripts\python.exe -m pip install -r requirements.txt&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;behave==1.2.6&lt;/code&gt; and &lt;code&gt;behave-django==1.4.0&lt;/code&gt; available in &lt;code&gt;BE-GBM\.venv&lt;/code&gt;.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Django system check&lt;/td&gt;
&lt;td&gt;&lt;code&gt;.\.venv\Scripts\python.exe manage.py check&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;System check identified no issues&lt;/code&gt;.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Behave BDD&lt;/td&gt;
&lt;td&gt;&lt;code&gt;.\.venv\Scripts\python.exe manage.py behave --simple --junit --junit-directory=bdd-reports&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;3 features passed, 12 scenarios passed, 57 steps passed, 0 failed.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Regression subset&lt;/td&gt;
&lt;td&gt;&lt;code&gt;.\.venv\Scripts\python.exe manage.py test authentication.tests.test_register_view authentication.tests.test_activation_views pengajuan.tests.test_views_admin&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;65 tests passed.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Locust package&lt;/td&gt;
&lt;td&gt;&lt;code&gt;.\.venv\Scripts\locust.exe --version&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;locust 2.43.4&lt;/code&gt;.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;GitLab CI lint&lt;/td&gt;
&lt;td&gt;GitLab &lt;code&gt;/ci/lint&lt;/code&gt; API with merged YAML&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;valid=True&lt;/code&gt;, &lt;code&gt;warnings=[]&lt;/code&gt;.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Staging register baseline&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;curl.exe -m 75 -w&lt;/code&gt; to &lt;code&gt;POST /api/auth/register/&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;HTTP 202 in 5.134493s.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;
&lt;h3&gt;
  
  
  Backend MR Pipeline 279225 Evidence
&lt;/h3&gt;

&lt;p&gt;Pipeline: &lt;a href="https://gitlab.cs.ui.ac.id/ppl-fasilkom-ui/2026/kelas-d/group1-gb/be-gbm/-/pipelines/279225" rel="noopener noreferrer"&gt;https://gitlab.cs.ui.ac.id/ppl-fasilkom-ui/2026/kelas-d/group1-gb/be-gbm/-/pipelines/279225&lt;/a&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Job&lt;/th&gt;
&lt;th&gt;Status&lt;/th&gt;
&lt;th&gt;Why It Matters&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;test&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;success&lt;/td&gt;
&lt;td&gt;Confirms the standard backend regression test suite still passes.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;bdd&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;success&lt;/td&gt;
&lt;td&gt;Confirms the executable BDD scenarios are runnable in CI.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;diff_coverage&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;success&lt;/td&gt;
&lt;td&gt;Confirms changed-code coverage requirements are enforced.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;semgrep-sast&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;success&lt;/td&gt;
&lt;td&gt;Confirms backend SAST analyzer runs in the MR pipeline.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;gemnasium-python-dependency_scanning&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;success&lt;/td&gt;
&lt;td&gt;Confirms Python dependency scanning runs in the MR pipeline.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;secret_detection&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;success&lt;/td&gt;
&lt;td&gt;Confirms committed secrets are scanned in the MR pipeline.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;debug-mr-analyzer&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;success&lt;/td&gt;
&lt;td&gt;Confirms the existing project diagnostic job still completes.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;sonarqube&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;failed&lt;/td&gt;
&lt;td&gt;Not used as evidence for this claim; the security, BDD, test, and coverage evidence above are the concrete data used here.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;
&lt;h3&gt;
  
  
  Frontend Evidence
&lt;/h3&gt;

&lt;p&gt;Frontend pipeline: &lt;a href="https://gitlab.cs.ui.ac.id/ppl-fasilkom-ui/2026/kelas-d/group1-gb/fe-gbm/-/pipelines/279116" rel="noopener noreferrer"&gt;https://gitlab.cs.ui.ac.id/ppl-fasilkom-ui/2026/kelas-d/group1-gb/fe-gbm/-/pipelines/279116&lt;/a&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Check&lt;/th&gt;
&lt;th&gt;Evidence&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;GitLab CI lint&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;valid=True&lt;/code&gt;, &lt;code&gt;warnings=[]&lt;/code&gt; for merged YAML.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;BDD generation&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;npm run e2e:bdd:gen&lt;/code&gt; succeeded.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Playwright test discovery&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;npx playwright test --list&lt;/code&gt; found 3 tests across 2 generated spec files.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Targeted ESLint&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;npx eslint e2e/steps/common.steps.ts playwright.config.ts&lt;/code&gt; succeeded.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;CI jobs&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;test&lt;/code&gt;, &lt;code&gt;e2e-bdd&lt;/code&gt;, &lt;code&gt;diff_coverage&lt;/code&gt;, &lt;code&gt;semgrep-sast&lt;/code&gt;, &lt;code&gt;gemnasium-dependency_scanning&lt;/code&gt;, &lt;code&gt;secret_detection&lt;/code&gt;, and &lt;code&gt;sonarqube&lt;/code&gt; succeeded.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;
&lt;h2&gt;
  
  
  Demonstration of Advanced Testing Tools
&lt;/h2&gt;
&lt;h3&gt;
  
  
  Security Testing
&lt;/h3&gt;

&lt;p&gt;Security testing is implemented through GitLab security templates and an OWASP ZAP baseline job.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Tool&lt;/th&gt;
&lt;th&gt;Where It Runs&lt;/th&gt;
&lt;th&gt;Evidence&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;GitLab SAST&lt;/td&gt;
&lt;td&gt;FE and BE MR pipelines&lt;/td&gt;
&lt;td&gt;FE &lt;code&gt;semgrep-sast&lt;/code&gt; success in pipeline 279116; BE &lt;code&gt;semgrep-sast&lt;/code&gt; success in pipeline 279225.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Dependency Scanning&lt;/td&gt;
&lt;td&gt;FE and BE MR pipelines&lt;/td&gt;
&lt;td&gt;FE &lt;code&gt;gemnasium-dependency_scanning&lt;/code&gt; success; BE &lt;code&gt;gemnasium-python-dependency_scanning&lt;/code&gt; success.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Secret Detection&lt;/td&gt;
&lt;td&gt;FE and BE MR pipelines&lt;/td&gt;
&lt;td&gt;FE and BE &lt;code&gt;secret_detection&lt;/code&gt; success.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;OWASP ZAP Baseline&lt;/td&gt;
&lt;td&gt;BE scheduled/staging-capable job&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;dast-zap-baseline&lt;/code&gt; stores &lt;code&gt;zap-report.html&lt;/code&gt; and &lt;code&gt;zap-report.json&lt;/code&gt; artifacts when executed.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Project benefit: security checks are no longer an informal review activity only. They are reproducible CI jobs and are complemented by executable abuse-case tests for the authentication and authorization paths that matter most to GBM.&lt;/p&gt;
&lt;h3&gt;
  
  
  Penetration Testing
&lt;/h3&gt;

&lt;p&gt;The penetration-testing evidence is implemented as abuse-case integration tests. This is intentionally more concrete than a manual checklist because every abuse path has an endpoint, an expected response, and a regression signal.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;#&lt;/th&gt;
&lt;th&gt;Risk&lt;/th&gt;
&lt;th&gt;Endpoint or Flow&lt;/th&gt;
&lt;th&gt;Expected Result&lt;/th&gt;
&lt;th&gt;Evidence&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;Broken Access Control&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;PATCH /api/pengajuan/admin/&amp;lt;uuid&amp;gt;/status/&lt;/code&gt; as non-admin&lt;/td&gt;
&lt;td&gt;403 Forbidden&lt;/td&gt;
&lt;td&gt;BDD scenario returns 403.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;Identification and Authentication Failure&lt;/td&gt;
&lt;td&gt;Repeated wrong-password login&lt;/td&gt;
&lt;td&gt;429 lockout&lt;/td&gt;
&lt;td&gt;BDD login lockout scenario returns 429.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;Insecure Design / Brute Force&lt;/td&gt;
&lt;td&gt;Repeated registration attempts&lt;/td&gt;
&lt;td&gt;429 rate limit&lt;/td&gt;
&lt;td&gt;BDD registration burst scenario returns 429.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;Mass Assignment&lt;/td&gt;
&lt;td&gt;Register with &lt;code&gt;role=ADMIN&lt;/code&gt; and superuser-like fields&lt;/td&gt;
&lt;td&gt;400 rejection and no forged admin&lt;/td&gt;
&lt;td&gt;BDD scenario asserts no forged admin account exists.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;Token Tampering&lt;/td&gt;
&lt;td&gt;Activation with modified token&lt;/td&gt;
&lt;td&gt;400 &lt;code&gt;TOKEN_INVALID&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;BDD activation scenario returns &lt;code&gt;TOKEN_INVALID&lt;/code&gt;.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;6&lt;/td&gt;
&lt;td&gt;Token Expiry&lt;/td&gt;
&lt;td&gt;Activation with expired token&lt;/td&gt;
&lt;td&gt;400 &lt;code&gt;TOKEN_EXPIRED&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;BDD activation scenario returns &lt;code&gt;TOKEN_EXPIRED&lt;/code&gt;.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;7&lt;/td&gt;
&lt;td&gt;Email Enumeration&lt;/td&gt;
&lt;td&gt;Duplicate registration&lt;/td&gt;
&lt;td&gt;Generic 202 response&lt;/td&gt;
&lt;td&gt;BDD scenario verifies no duplicate/existing wording leaks account existence.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;8&lt;/td&gt;
&lt;td&gt;Invalid Admin State Transition&lt;/td&gt;
&lt;td&gt;Admin rejects/approves invalid status transition&lt;/td&gt;
&lt;td&gt;400 validation failure&lt;/td&gt;
&lt;td&gt;BDD admin scenario verifies invalid transition rejection.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Project benefit: these tests cover attacker behavior, not only happy-path behavior. They protect the project from regressions where a normal feature change accidentally weakens authorization, throttling, token validation, or privacy-preserving registration responses.&lt;/p&gt;
&lt;h3&gt;
  
  
  BDD
&lt;/h3&gt;

&lt;p&gt;BDD is used as executable specification for critical business behavior.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Layer&lt;/th&gt;
&lt;th&gt;Tool&lt;/th&gt;
&lt;th&gt;Scope&lt;/th&gt;
&lt;th&gt;Evidence&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Backend&lt;/td&gt;
&lt;td&gt;Behave Django&lt;/td&gt;
&lt;td&gt;Registration, duplicate email behavior, activation token validity, login lockout, admin authorization, approve/reject transitions&lt;/td&gt;
&lt;td&gt;3 features, 12 scenarios, 57 steps passed.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Frontend&lt;/td&gt;
&lt;td&gt;Playwright BDD&lt;/td&gt;
&lt;td&gt;User-visible registration and admin submission flows backed by API setup&lt;/td&gt;
&lt;td&gt;3 generated Playwright BDD tests discovered; &lt;code&gt;e2e-bdd&lt;/code&gt; passed in CI.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Project benefit: reviewers can read behavior in Gherkin while the CI/local commands verify the same behavior against real code paths. This reduces the gap between documentation, acceptance criteria, and regression tests.&lt;/p&gt;
&lt;h3&gt;
  
  
  Stress Testing
&lt;/h3&gt;

&lt;p&gt;Stress testing is implemented with Locust because it models user behavior as Python &lt;code&gt;HttpUser&lt;/code&gt; classes and can be run headlessly with CSV/HTML artifacts.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Locust User&lt;/th&gt;
&lt;th&gt;Behavior Modeled&lt;/th&gt;
&lt;th&gt;Authentication Need&lt;/th&gt;
&lt;th&gt;Expected Output&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;RegistrationBurstUser&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;High-volume semester-start registration&lt;/td&gt;
&lt;td&gt;No login required because registration is public&lt;/td&gt;
&lt;td&gt;Request rate, failure rate, latency percentiles, HTML/CSV report.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;AdminDashboardUser&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Admin login, pending submission listing, optional status update&lt;/td&gt;
&lt;td&gt;Requires &lt;code&gt;LOCUST_ADMIN_EMAIL&lt;/code&gt; and &lt;code&gt;LOCUST_ADMIN_PASSWORD&lt;/code&gt;; optional &lt;code&gt;LOCUST_ADMIN_PENGAJUAN_ID&lt;/code&gt; is a pending submission UUID, not an admin user id&lt;/td&gt;
&lt;td&gt;Authenticated admin endpoint latency and failure report.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;KegiatanStressTestUser&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Authenticated kegiatan/statistik/periode access&lt;/td&gt;
&lt;td&gt;Requires one or more user credentials through &lt;code&gt;LOCUST_EMAIL&lt;/code&gt;/&lt;code&gt;LOCUST_PASSWORD&lt;/code&gt; or numbered credential variables&lt;/td&gt;
&lt;td&gt;Authenticated API stress profile.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;GBMQualityAssuranceUser&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Mixed endpoint profile across major backend API paths&lt;/td&gt;
&lt;td&gt;Requires authenticated user credentials&lt;/td&gt;
&lt;td&gt;Cross-endpoint baseline load profile.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Example commands:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="n"&gt;python&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-m&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;locust&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-f&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;locustfile.py&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;RegistrationBurstUser&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;--host&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;https://gbim-staging.ppl.cs.ui.ac.id&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;--headless&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-u&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;50&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-r&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;10&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;--run-time&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;2m&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;--csv&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;locust_register&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;--html&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;locust_register.html&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;python&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-m&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;locust&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-f&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;locustfile.py&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;AdminDashboardUser&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;--host&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;https://gbim-staging.ppl.cs.ui.ac.id&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;--headless&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-u&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;20&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-r&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;5&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;--run-time&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;2m&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;--csv&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;locust_admin&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;--html&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;locust_admin.html&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;python&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-m&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;locust&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-f&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;locustfile.py&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;KegiatanStressTestUser&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;--host&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;https://gbim-staging.ppl.cs.ui.ac.id&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;--headless&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-u&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;20&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-r&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;5&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;--run-time&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;2m&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;--csv&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;locust_kegiatan&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;--html&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;locust_kegiatan.html&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Claim boundary: this blog does not claim that staging meets p95 below 500ms. The measurable claim is that stress-test workloads are defined using realistic user behavior, can be executed manually, produce machine-readable CSV plus reviewable HTML reports, and correctly separate expected protection responses from unexpected failures.&lt;/p&gt;

&lt;h2&gt;
  
  
  Measurable Project Benefits
&lt;/h2&gt;

&lt;p&gt;This section compares the project before IR B5 More Testing with the final implemented state.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Before IR B5 More Testing&lt;/th&gt;
&lt;th&gt;After Final Implementation&lt;/th&gt;
&lt;th&gt;Concrete Data&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Critical registration, activation, and admin behavior was not documented as one executable specification for this claim.&lt;/td&gt;
&lt;td&gt;Backend and frontend BDD now describe the critical journeys in Gherkin and execute them against real application behavior.&lt;/td&gt;
&lt;td&gt;BE: 3 features, 12 scenarios, 57 steps passed. FE: 3 Playwright BDD tests discovered and &lt;code&gt;e2e-bdd&lt;/code&gt; passed.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Security testing evidence was not consolidated into repeatable MR-level analyzer jobs plus explicit abuse-case scenarios.&lt;/td&gt;
&lt;td&gt;GitLab security analyzers run in CI, while abuse cases verify access control, throttling, token validity, and mass-assignment protection.&lt;/td&gt;
&lt;td&gt;FE pipeline 279116 security jobs passed. BE pipeline 279225 &lt;code&gt;semgrep-sast&lt;/code&gt;, &lt;code&gt;gemnasium-python-dependency_scanning&lt;/code&gt;, and &lt;code&gt;secret_detection&lt;/code&gt; passed.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Penetration-testing evidence was not mapped from risks to endpoints, expected statuses, and regression checks.&lt;/td&gt;
&lt;td&gt;8 OWASP-mapped abuse paths now specify the endpoint/flow, expected result, and executable evidence.&lt;/td&gt;
&lt;td&gt;403 for non-admin access, 429 for lockout/rate-limit, 400 for invalid/tampered token paths, generic 202 for duplicate registration.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Duplicate registration behavior was not presented as privacy/security evidence for this claim.&lt;/td&gt;
&lt;td&gt;Duplicate email registration now has an explicit BDD assertion that the response remains generic and does not leak account existence.&lt;/td&gt;
&lt;td&gt;Scenario &lt;code&gt;Duplicate email registration does not leak account existence&lt;/code&gt; returns 202 and checks response wording.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Activation-token abuse behavior was not presented as a complete negative-path evidence set.&lt;/td&gt;
&lt;td&gt;Invalid and expired activation token paths are explicitly tested.&lt;/td&gt;
&lt;td&gt;BDD verifies &lt;code&gt;TOKEN_INVALID&lt;/code&gt; and &lt;code&gt;TOKEN_EXPIRED&lt;/code&gt;.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Frontend testing evidence did not show API-backed BDD plus security scanning in one MR pipeline.&lt;/td&gt;
&lt;td&gt;The FE MR pipeline runs unit/quality jobs, BDD generation/discovery, and security analyzers.&lt;/td&gt;
&lt;td&gt;Pipeline 279116: &lt;code&gt;test&lt;/code&gt;, &lt;code&gt;e2e-bdd&lt;/code&gt;, &lt;code&gt;diff_coverage&lt;/code&gt;, &lt;code&gt;sonarqube&lt;/code&gt;, SAST, dependency scanning, and secret detection succeeded.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Stress testing was not expressed as named user workloads tied to GBM roles and endpoints.&lt;/td&gt;
&lt;td&gt;Locust now models registration burst, admin dashboard, authenticated kegiatan, and mixed QA workloads.&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;locustfile.py&lt;/code&gt; defines &lt;code&gt;RegistrationBurstUser&lt;/code&gt;, &lt;code&gt;AdminDashboardUser&lt;/code&gt;, &lt;code&gt;KegiatanStressTestUser&lt;/code&gt;, and &lt;code&gt;GBMQualityAssuranceUser&lt;/code&gt;.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Rate limiting could be misread as a failure in high-load or abuse scenarios.&lt;/td&gt;
&lt;td&gt;Expected protection is distinguished from unexpected failure.&lt;/td&gt;
&lt;td&gt;BDD expects 429 in abuse/rate-limit cases; Locust treats 202 and 429 as acceptable for registration burst.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Literature and Best Practices
&lt;/h2&gt;

&lt;p&gt;The implementation follows current industry guidance by turning general testing advice into concrete project controls.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Reference&lt;/th&gt;
&lt;th&gt;Best-Practice Principle&lt;/th&gt;
&lt;th&gt;Project Application&lt;/th&gt;
&lt;th&gt;Correlating Evidence&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;OWASP Top 10 2021&lt;/td&gt;
&lt;td&gt;Web applications should explicitly test risks such as broken access control, insecure design, identification/authentication failures, and software/data integrity failures.&lt;/td&gt;
&lt;td&gt;Abuse cases target non-admin access, login lockout, registration throttling, token tampering/expiry, and mass assignment.&lt;/td&gt;
&lt;td&gt;BDD scenarios return 403, 429, 400, &lt;code&gt;TOKEN_INVALID&lt;/code&gt;, &lt;code&gt;TOKEN_EXPIRED&lt;/code&gt;, and generic 202 as expected.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;OWASP ASVS&lt;/td&gt;
&lt;td&gt;Security verification should be expressed as testable technical controls, not only policy statements.&lt;/td&gt;
&lt;td&gt;Authentication, token lifecycle, authorization, and privacy-preserving registration behavior are verified as executable tests.&lt;/td&gt;
&lt;td&gt;Backend BDD: 12 scenarios and 57 steps passed.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;OWASP Web Security Testing Guide&lt;/td&gt;
&lt;td&gt;Security testing should include misuse and invalid-use cases that reveal enumeration, weak defenses, and abuse of valid functionality.&lt;/td&gt;
&lt;td&gt;Duplicate registration, brute-force login, register burst, invalid token, expired token, and non-admin admin requests are tested directly.&lt;/td&gt;
&lt;td&gt;8 abuse paths in the penetration-testing evidence.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;OWASP Abuse Case Cheat Sheet&lt;/td&gt;
&lt;td&gt;Abuse cases help convert attacker goals into concrete requirements and tests.&lt;/td&gt;
&lt;td&gt;The project defines attacker-style behaviors such as forged admin registration, token tampering, duplicate email enumeration, and unauthorized admin status changes.&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;pen-test-report.md&lt;/code&gt; and BDD feature files map abuse behavior to expected API responses.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;GitLab Application Security&lt;/td&gt;
&lt;td&gt;Security scanning should be integrated into the development workflow so findings are repeatable and visible during merge requests.&lt;/td&gt;
&lt;td&gt;SAST, dependency scanning, and secret detection are configured in FE and BE CI.&lt;/td&gt;
&lt;td&gt;FE pipeline 279116 and BE pipeline 279225 security analyzer jobs succeeded.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;OWASP ZAP Baseline Scan&lt;/td&gt;
&lt;td&gt;Passive DAST scanning is suitable for a safe baseline assessment of deployed/staging web applications.&lt;/td&gt;
&lt;td&gt;The backend CI includes a &lt;code&gt;dast-zap-baseline&lt;/code&gt; job that produces HTML/JSON artifacts when run against staging.&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;.gitlab-ci.yml&lt;/code&gt; defines &lt;code&gt;zap-report.html&lt;/code&gt; and &lt;code&gt;zap-report.json&lt;/code&gt; artifacts.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cucumber / Gherkin BDD&lt;/td&gt;
&lt;td&gt;Requirements should be readable as executable specifications that validate actual software behavior.&lt;/td&gt;
&lt;td&gt;Registration, activation, and admin flows are written as Gherkin features and executed through Behave/Playwright.&lt;/td&gt;
&lt;td&gt;BE 3 features passed; FE BDD generation and discovery succeeded.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Playwright Test Isolation&lt;/td&gt;
&lt;td&gt;Browser contexts isolate state so tests do not leak cookies/session data into each other.&lt;/td&gt;
&lt;td&gt;FE BDD uses isolated browser/page state and API-backed setup for role-specific flows.&lt;/td&gt;
&lt;td&gt;3 Playwright BDD tests discovered and CI &lt;code&gt;e2e-bdd&lt;/code&gt; succeeded.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Locust Documentation&lt;/td&gt;
&lt;td&gt;Load tests should model user behavior with tasks, wait time, headless execution, and exportable statistics.&lt;/td&gt;
&lt;td&gt;Locust users model registration, admin dashboard, kegiatan, and QA workloads; commands produce CSV and HTML reports.&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;locustfile.py&lt;/code&gt; uses &lt;code&gt;HttpUser&lt;/code&gt;, &lt;code&gt;@task&lt;/code&gt;, &lt;code&gt;between&lt;/code&gt;, &lt;code&gt;on_start&lt;/code&gt;, &lt;code&gt;--headless&lt;/code&gt;, &lt;code&gt;--csv&lt;/code&gt;, and &lt;code&gt;--html&lt;/code&gt;.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Martin Fowler's Practical Test Pyramid&lt;/td&gt;
&lt;td&gt;A healthy test strategy keeps many lower-level tests while adding only targeted end-to-end/BDD tests for high-value flows.&lt;/td&gt;
&lt;td&gt;IR B5 does not replace existing unit/API tests; it adds focused BDD and security scenarios for critical journeys.&lt;/td&gt;
&lt;td&gt;65 backend regression tests passed in addition to 12 BDD scenarios.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  Why These References Matter for GBM
&lt;/h3&gt;

&lt;p&gt;The references are not used as generic citations only. Each one changes how testing is applied in this project:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;OWASP Top 10, ASVS, WSTG, and Abuse Case guidance shift testing from happy-path verification to attacker-aware verification.&lt;/li&gt;
&lt;li&gt;GitLab Application Security shifts security testing from occasional manual review to repeatable MR evidence.&lt;/li&gt;
&lt;li&gt;Cucumber/Gherkin shifts business-critical behavior from scattered implementation knowledge to readable executable specifications.&lt;/li&gt;
&lt;li&gt;Playwright isolation prevents frontend BDD from becoming flaky due to leaked session state between admin and non-admin flows.&lt;/li&gt;
&lt;li&gt;Locust keeps stress testing realistic by modeling user roles and wait times instead of sending context-free requests.&lt;/li&gt;
&lt;li&gt;The Test Pyramid keeps the suite economically sane: unit/API tests remain broad, while BDD/E2E tests are targeted to the highest-risk workflows.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;References:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;OWASP Top 10 2021: &lt;a href="https://owasp.org/Top10/2021/" rel="noopener noreferrer"&gt;https://owasp.org/Top10/2021/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;OWASP ASVS: &lt;a href="https://owasp.org/www-project-application-security-verification-standard/" rel="noopener noreferrer"&gt;https://owasp.org/www-project-application-security-verification-standard/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;OWASP Web Security Testing Guide, application misuse testing: &lt;a href="https://owasp.org/www-project-web-security-testing-guide/latest/4-Web_Application_Security_Testing/10-Business_Logic_Testing/07-Test_Defenses_Against_Application_Misuse" rel="noopener noreferrer"&gt;https://owasp.org/www-project-web-security-testing-guide/latest/4-Web_Application_Security_Testing/10-Business_Logic_Testing/07-Test_Defenses_Against_Application_Misuse&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;OWASP Abuse Case Cheat Sheet: &lt;a href="https://cheatsheetseries.owasp.org/cheatsheets/Abuse_Case_Cheat_Sheet.html" rel="noopener noreferrer"&gt;https://cheatsheetseries.owasp.org/cheatsheets/Abuse_Case_Cheat_Sheet.html&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;GitLab SAST: &lt;a href="https://docs.gitlab.com/ee/user/application_security/sast/" rel="noopener noreferrer"&gt;https://docs.gitlab.com/ee/user/application_security/sast/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;GitLab Application Security detection: &lt;a href="https://docs.gitlab.com/user/application_security/detect/" rel="noopener noreferrer"&gt;https://docs.gitlab.com/user/application_security/detect/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;OWASP ZAP Baseline Scan: &lt;a href="https://www.zaproxy.org/docs/docker/baseline-scan/" rel="noopener noreferrer"&gt;https://www.zaproxy.org/docs/docker/baseline-scan/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Cucumber BDD overview: &lt;a href="https://cucumber.io/docs/guides/overview/" rel="noopener noreferrer"&gt;https://cucumber.io/docs/guides/overview/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Playwright test isolation: &lt;a href="https://playwright.dev/docs/browser-contexts" rel="noopener noreferrer"&gt;https://playwright.dev/docs/browser-contexts&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Locust documentation: &lt;a href="https://docs.locust.io/en/stable/" rel="noopener noreferrer"&gt;https://docs.locust.io/en/stable/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Locust configuration and report options: &lt;a href="https://docs.locust.io/en/stable/configuration.html" rel="noopener noreferrer"&gt;https://docs.locust.io/en/stable/configuration.html&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Martin Fowler, Practical Test Pyramid: &lt;a href="https://martinfowler.com/articles/practical-test-pyramid.html" rel="noopener noreferrer"&gt;https://martinfowler.com/articles/practical-test-pyramid.html&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Critique of Previous Testing and Quality Improvements
&lt;/h2&gt;

&lt;p&gt;This critique compares the condition before IR B5 More Testing existed with the final implemented state.&lt;/p&gt;

&lt;h3&gt;
  
  
  Critique 1: Critical flows were not represented as consolidated executable specifications
&lt;/h3&gt;

&lt;p&gt;Before IR B5, critical behavior such as registration, activation, login lockout, and admin approval was not presented as one readable executable specification for reviewers. The risk was not that the project had no tests at all; the risk was that the business-critical acceptance behavior was spread across implementation-level tests and was harder to review as user journeys.&lt;/p&gt;

&lt;p&gt;Final improvement: backend and frontend BDD now describe those journeys in Gherkin and execute them against real system behavior.&lt;/p&gt;

&lt;p&gt;Evidence:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Backend Behave: 3 features, 12 scenarios, 57 steps passed.&lt;/li&gt;
&lt;li&gt;Frontend Playwright BDD: 3 tests discovered; &lt;code&gt;e2e-bdd&lt;/code&gt; passed in pipeline 279116.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Critique 2: Security behavior was not presented as explicit abuse-case coverage
&lt;/h3&gt;

&lt;p&gt;Before IR B5, the security posture for this claim was not organized around attacker behavior. A reviewer could not quickly trace OWASP risks to endpoint-level expected responses.&lt;/p&gt;

&lt;p&gt;Final improvement: the penetration-testing evidence now maps attacker-style actions to concrete API behavior: non-admin access is rejected, lockout/rate limit is enforced, forged admin registration is rejected, tampered/expired tokens fail safely, and duplicate registration avoids account enumeration.&lt;/p&gt;

&lt;p&gt;Evidence:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;8 abuse paths documented and implemented.&lt;/li&gt;
&lt;li&gt;Expected responses include 403, 429, 400, &lt;code&gt;TOKEN_INVALID&lt;/code&gt;, &lt;code&gt;TOKEN_EXPIRED&lt;/code&gt;, and generic 202.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Critique 3: Security scanning was not consistently demonstrated as MR evidence for both FE and BE
&lt;/h3&gt;

&lt;p&gt;Before IR B5, security evidence was not presented as a complete MR-level story across frontend and backend.&lt;/p&gt;

&lt;p&gt;Final improvement: FE and BE now both show CI-level security analyzer execution. FE pipeline 279116 passed the security jobs, while BE pipeline 279225 passed SAST, dependency scanning, and secret detection.&lt;/p&gt;

&lt;p&gt;Evidence:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;FE pipeline 279116: &lt;code&gt;semgrep-sast&lt;/code&gt;, &lt;code&gt;gemnasium-dependency_scanning&lt;/code&gt;, and &lt;code&gt;secret_detection&lt;/code&gt; success.&lt;/li&gt;
&lt;li&gt;BE pipeline 279225: &lt;code&gt;semgrep-sast&lt;/code&gt;, &lt;code&gt;gemnasium-python-dependency_scanning&lt;/code&gt;, and &lt;code&gt;secret_detection&lt;/code&gt; success.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Critique 4: Stress testing was not tied to realistic GBM user behavior
&lt;/h3&gt;

&lt;p&gt;Before IR B5, stress testing evidence was not expressed as clear GBM user workloads tied to roles, endpoints, and operational scenarios.&lt;/p&gt;

&lt;p&gt;Final improvement: Locust now defines named workloads for semester-start registration, admin dashboard usage, authenticated kegiatan access, and mixed QA behavior.&lt;/p&gt;

&lt;p&gt;Evidence:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;RegistrationBurstUser&lt;/code&gt; models public registration load.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;AdminDashboardUser&lt;/code&gt; models authenticated admin behavior.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;KegiatanStressTestUser&lt;/code&gt; models authenticated kegiatan/statistik/periode access.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;GBMQualityAssuranceUser&lt;/code&gt; models a mixed backend API baseline.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Critique 5: Expected protection responses needed to be separated from real failures
&lt;/h3&gt;

&lt;p&gt;Before IR B5, high-load and abuse-case evidence could be misunderstood if every 429 response was treated as a failure. In this project, 429 can be the correct result because it means the rate limiter is protecting authentication or registration endpoints.&lt;/p&gt;

&lt;p&gt;Final improvement: BDD and Locust distinguish expected protection from unexpected failure.&lt;/p&gt;

&lt;p&gt;Evidence:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;BDD expects 429 for login lockout and register burst controls.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;RegistrationBurstUser&lt;/code&gt; treats 202 and 429 as acceptable outcomes for registration burst behavior.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Claim Boundaries
&lt;/h2&gt;

&lt;p&gt;This blog deliberately does not claim the following:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;It does not claim that staging stress testing meets p95 below 500ms.&lt;/li&gt;
&lt;li&gt;It does not claim that stress tests should block every merge request.&lt;/li&gt;
&lt;li&gt;It does not claim that BDD replaces unit/API tests.&lt;/li&gt;
&lt;li&gt;It does not claim that security scanners prove the application has no vulnerabilities.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The actual claim is narrower and evidence-based: advanced testing tools are implemented, their outputs are understood, and their benefits are tied to concrete project data.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;The IR B5 More Testing claim is satisfied because:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Advanced tools are implemented and used: Behave, Playwright BDD, GitLab SAST, Dependency Scanning, Secret Detection, OWASP ZAP baseline, and Locust.&lt;/li&gt;
&lt;li&gt;The outputs are understood: 403 means access control is enforced, 429 means expected protection under abuse/load, 400 with token-specific codes means invalid activation is rejected safely, and security analyzer jobs provide repeatable MR evidence.&lt;/li&gt;
&lt;li&gt;The benefits are measurable: 12 backend BDD scenarios passed, 65 backend regression tests passed, frontend pipeline 279116 passed BDD/security/quality checks, and backend pipeline 279225 passed test, BDD, coverage, and security analyzer jobs.&lt;/li&gt;
&lt;li&gt;The work aligns with literature and current best practices: OWASP for abuse/security testing, GitLab for repeatable DevSecOps scanning, Cucumber for executable specifications, Playwright for isolated browser tests, Locust for user-behavior load modeling, and the Test Pyramid for balanced test economics.&lt;/li&gt;
&lt;li&gt;The critique is based on the project state before IR B5 implementation and the final state after implementation.&lt;/li&gt;
&lt;/ol&gt;

</description>
    </item>
    <item>
      <title>Optimizing Test Design Mutation Testing, ISP, and CFG in the GBIM Project</title>
      <dc:creator>Vincent Davis</dc:creator>
      <pubDate>Mon, 11 May 2026 07:12:12 +0000</pubDate>
      <link>https://forem.com/vincent_davis_7b6665836a9/optimizing-test-design-mutation-testing-isp-and-cfg-in-the-gbim-project-13d5</link>
      <guid>https://forem.com/vincent_davis_7b6665836a9/optimizing-test-design-mutation-testing-isp-and-cfg-in-the-gbim-project-13d5</guid>
      <description>&lt;p&gt;&lt;strong&gt;Vincent Davis Leonard&lt;/strong&gt; &lt;/p&gt;




&lt;h2&gt;
  
  
  TL;DR
&lt;/h2&gt;

&lt;p&gt;I applied three test design optimization techniques (Input Space Partitioning or ISP, Control Flow Graph or CFG Analysis, and Mutation Testing) to the four main features I manage in the GBIM project. These features include account registration, account activation via token, account verification by admin, and the approval or rejection of submissions. The results include 33 new tests in the backend, 3 commits in the frontend, an expanded mutation scope, and the addition of Stryker threshold enforcement in the frontend mutation testing configuration.&lt;/p&gt;




&lt;h2&gt;
  
  
  Tools and Methods Used
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Input Space Partitioning (ISP)
&lt;/h3&gt;

&lt;p&gt;ISP (Ammann &amp;amp; Offutt, &lt;em&gt;Introduction to Software Testing&lt;/em&gt;, 2016) is a technique that divides the input domain of a function into equivalence classes. These classes are groups of values expected to be treated similarly by the system. Instead of trying all combinations, we select one representative value per partition.&lt;/p&gt;

&lt;p&gt;I used the base-choice coverage strategy. We choose one valid value as a baseline and then vary one characteristic per test. This approach is efficient without falling into combinatorial explosion.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Tools used&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Manual analysis of the source code (&lt;code&gt;authentication/serializers.py&lt;/code&gt;, &lt;code&gt;RegisterForm.tsx&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;Annotation &lt;code&gt;# ISP &amp;lt;characteristic&amp;gt;.&amp;lt;partition&amp;gt;&lt;/code&gt; in each test for traceability&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Examples of partitioned characteristics for the Register feature&lt;/strong&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Characteristic&lt;/th&gt;
&lt;th&gt;Partition&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Email&lt;/td&gt;
&lt;td&gt;valid, no &lt;code&gt;@&lt;/code&gt;, &amp;gt;254 char, whitespace&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Password&lt;/td&gt;
&lt;td&gt;&amp;lt;8 char, exactly 8 valid, exactly 7 invalid, no digit, no uppercase, valid strong&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Role&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;KAPRODI&lt;/code&gt;, &lt;code&gt;GURU_BESAR&lt;/code&gt;, &lt;code&gt;ADMIN&lt;/code&gt; (blocked), invalid enum&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Activation Token&lt;/td&gt;
&lt;td&gt;valid fresh, expired, already used, malformed, missing&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The base-choice input was an actual valid &lt;code&gt;KAPRODI&lt;/code&gt; registration payload, not only an abstract test idea:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;valid_data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;email&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;kaprodi@univ.ac.id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;password&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Password123!&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;name&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Bapak Kaprodi&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;role&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;RoleChoices&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;KAPRODI&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;telephone&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;08123456789&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;perguruan_tinggi&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Universitas Indonesia&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;program_studi&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Ilmu Komputer&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;provinsi&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Jawa Barat&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;kabupaten_kota&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Depok&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each ISP test copies this base input and changes one characteristic. For example, the password boundary is tested by comparing exactly 8 characters against exactly 7 characters:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_serializer_accepts_password_exactly_8_chars&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="c1"&gt;# ISP: Password.exactly_8_chars_valid
&lt;/span&gt;    &lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;valid_data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;copy&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;password&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Passw0rd&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;

    &lt;span class="n"&gt;serializer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;RegisterSerializer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;assertTrue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;serializer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;is_valid&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_serializer_rejects_password_exactly_7_chars&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="c1"&gt;# ISP: Password.exactly_7_chars_invalid
&lt;/span&gt;    &lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;valid_data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;copy&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;password&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Pas0rd!&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;

    &lt;span class="n"&gt;serializer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;RegisterSerializer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;assertFalse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;serializer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;is_valid&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
    &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;assertIn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;password&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;serializer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;errors&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  2. Control Flow Graph (CFG) Analysis
&lt;/h3&gt;

&lt;p&gt;CFG (Ammann &amp;amp; Offutt, ch.7) represents the program execution flow as a graph where each node is a basic block and each edge is a conditional branch. From the CFG we identify &lt;strong&gt;prime paths&lt;/strong&gt; which are the shortest non-repeating paths through all nodes.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Target module &lt;code&gt;_validate_transition&lt;/code&gt; (&lt;code&gt;pengajuan/services.py 66-75&lt;/code&gt;)&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The actual code&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="mi"&gt;66&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;_validate_transition&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;previous_status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;new_status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="mi"&gt;67&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;     &lt;span class="nf"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;previous_status&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;new_status&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;ALLOWED_TRANSITIONS&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="mi"&gt;68&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;         &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;ValidationError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;span class="mi"&gt;69&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;             &lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="mi"&gt;70&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;                 &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;status&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
&lt;span class="mi"&gt;71&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;                     &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Transisi status dari &lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;previous_status&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt; ke &lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;new_status&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt; &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="mi"&gt;72&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;                     &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;tidak diperbolehkan.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="mi"&gt;73&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;                 &lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="mi"&gt;74&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;             &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="mi"&gt;75&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;         &lt;span class="p"&gt;)&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The CFG of this code (node = line of code, edge = execution flow)&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fljmwuxyz6esbxr3xkd63.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fljmwuxyz6esbxr3xkd63.png" alt=" " width="578" height="987"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Prime paths&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Path&lt;/th&gt;
&lt;th&gt;Condition&lt;/th&gt;
&lt;th&gt;Test&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1→2→3→5&lt;/td&gt;
&lt;td&gt;transition is not in &lt;code&gt;ALLOWED_TRANSITIONS&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;test_disetujui_to_menunggu_raises&lt;/code&gt;, &lt;code&gt;test_menunggu_to_menunggu_raises&lt;/code&gt;, etc&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1→2→4→5&lt;/td&gt;
&lt;td&gt;transition is in &lt;code&gt;ALLOWED_TRANSITIONS&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;test_menunggu_to_disetujui&lt;/code&gt;, &lt;code&gt;test_menunggu_to_ditolak&lt;/code&gt;, etc&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;This state machine CFG has 4 legal transitions and 5+ illegal transitions that all must have tests.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Tools&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Manual source code analysis + Mermaid diagram (planned in the &lt;code&gt;cfg/&lt;/code&gt; folder)&lt;/li&gt;
&lt;li&gt;Annotation &lt;code&gt;# CFG: from_state→to_state&lt;/code&gt; in the tests&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  3. Mutation Testing
&lt;/h3&gt;

&lt;p&gt;Mutation testing (Jia &amp;amp; Harman, IEEE TSE 2011) measures the quality of a test suite by injecting small defects or mutants into the source code. Examples include changing &lt;code&gt;&amp;gt;&lt;/code&gt; to &lt;code&gt;&amp;gt;=&lt;/code&gt;, or removing a condition. The process then checks if the test suite detects it (meaning the mutant is "killed"). The mutation score is calculated by dividing the killed mutants by the total mutants.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Tools&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Backend&lt;/strong&gt; &lt;code&gt;mutmut&lt;/code&gt; (Python) with operators including AOR, LCR, ROR, and statement deletion&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Frontend&lt;/strong&gt; Stryker Mutator (JS/TS) with operators including arithmetic, logical, equality, string, and array&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Both tools are complementary. The &lt;code&gt;mutmut&lt;/code&gt; tool is more aggressive in statement deletion, while Stryker is richer in JS/TS level operators.&lt;/p&gt;


&lt;h2&gt;
  
  
  Application to the Project and Evidence of Improvement
&lt;/h2&gt;
&lt;h3&gt;
  
  
  ISP Application
&lt;/h3&gt;

&lt;p&gt;Before this sprint, the &lt;code&gt;test_register_serializers.py&lt;/code&gt; test only covered the happy path and one or two errors. After the ISP audit, the following changes were made.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;New backend tests added&lt;/strong&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;File&lt;/th&gt;
&lt;th&gt;New Partitions&lt;/th&gt;
&lt;th&gt;Count&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;test_register_serializers.py&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;email whitespace, email &amp;gt;254 char, inactive duplicate email, password exactly 8, password no digit, password no uppercase, password whitespace, role ADMIN blocked, role null, telephone invalid format&lt;/td&gt;
&lt;td&gt;13&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;test_activation_views.py&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;token malformed, account already active&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;test_views_admin_account_verification.py&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;filter role invalid enum, filter status invalid enum, search no match, pagination beyond max&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;test_views_admin_account_verification_detail.py&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;approve AKTIF (idempotent), approve DITOLAK (reactivation), reject DITOLAK, reject non-existent, unauthorized non-admin&lt;/td&gt;
&lt;td&gt;9&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The backend changes were not just additional files; the tests explicitly encode the ISP partitions. For example, these tests cover email formatting, password boundary, and blocked roles in &lt;code&gt;RegisterSerializer&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_serializer_rejects_whitespace_only_email&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="c1"&gt;# ISP: Email.whitespace_only
&lt;/span&gt;    &lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;valid_data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;copy&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;email&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;   &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;

    &lt;span class="n"&gt;serializer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;RegisterSerializer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;assertFalse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;serializer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;is_valid&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
    &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;assertIn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;email&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;serializer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;errors&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_serializer_accepts_password_exactly_8_chars&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="c1"&gt;# ISP: Password.exactly_8_chars_valid
&lt;/span&gt;    &lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;valid_data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;copy&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;password&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Passw0rd&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;

    &lt;span class="n"&gt;serializer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;RegisterSerializer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;assertTrue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;serializer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;is_valid&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_serializer_rejects_admin_registration&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="c1"&gt;# ISP: Role.admin_blocked
&lt;/span&gt;    &lt;span class="n"&gt;admin_data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;valid_data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;copy&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;admin_data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;pop&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;telephone&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;admin_data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;role&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;RoleChoices&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ADMIN&lt;/span&gt;

    &lt;span class="n"&gt;serializer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;RegisterSerializer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;admin_data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;assertFalse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;serializer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;is_valid&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
    &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;assertEqual&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="nf"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;serializer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;errors&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;role&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]),&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Registrasi peran Admin tidak diizinkan melalui endpoint ini.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The same ISP style was also applied outside registration. For activation, malformed tokens are tested as a separate token partition:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_activation_rejects_malformed_token&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="c1"&gt;# ISP: Token.malformed
&lt;/span&gt;    &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;activation_url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;token&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;not-a-valid-token&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;

    &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;assertEqual&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;status_code&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;HTTP_400_BAD_REQUEST&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For admin verification, invalid filters and empty search results are tested explicitly so the admin list endpoint does not silently accept invalid query parameters:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_filter_status_invalid_enum_returns_bad_request&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="c1"&gt;# ISP: Filter.status_invalid_enum
&lt;/span&gt;    &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;force_authenticate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;admin&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;status&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;UNKNOWN&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;

    &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;assertEqual&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;status_code&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;HTTP_400_BAD_REQUEST&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;New frontend tests added&lt;/strong&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;File&lt;/th&gt;
&lt;th&gt;New Partitions&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;RegisterForm.test.tsx&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;API 429 rate limit response (ISP ApiError.status.429)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;useUpdateStatusPengajuan.test.ts&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;MENUNGGU to DITOLAK transition (CFG happy-path DITOLAK)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;For the frontend, the newly added ISP partition checks that a rate-limit response is displayed as a formatted client error, not as an unstructured JSON dump:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="c1"&gt;// ISP: ApiError.status.429 - rate limited response shows formatted body&lt;/span&gt;
&lt;span class="nf"&gt;it&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;renders formatted body for a 429 rate-limit error&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;apiErr&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;ApiError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;429&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Too Many Requests&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;detail&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Terlalu banyak percobaan.&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="nx"&gt;mockUseRegister&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;mockReturnValue&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;register&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;mockRegister&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;loading&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;apiErr&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="nf"&gt;render&lt;/span&gt;&lt;span class="p"&gt;(&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;RegisterForm&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;);&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;alert&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;screen&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getByRole&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;alert&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;alert&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toHaveTextContent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;detail: Terlalu banyak percobaan.&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;alert&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;not&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toHaveTextContent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;{&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  CFG Application
&lt;/h3&gt;

&lt;p&gt;Previously, &lt;code&gt;StatusChangeService._validate_transition&lt;/code&gt; was only tested for 2 to 3 legal transitions. After the CFG analysis, I added tests for all 5 illegal transitions.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fti71t9txomeb0px5wgfe.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fti71t9txomeb0px5wgfe.png" alt=" " width="698" height="1265"&gt;&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_illegal_transition_menunggu_to_menunggu_raises_validation_error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="c1"&gt;# CFG: MENUNGGU→MENUNGGU (self-loop)
&lt;/span&gt;    &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;pengajuan&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Pengajuan&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Status&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;MENUNGGU&lt;/span&gt;
    &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;pengajuan&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;save&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;update_fields&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;status&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
    &lt;span class="n"&gt;service&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;_build_service&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;assertRaises&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ValidationError&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;service&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;update_status&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;pengajuan&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Pengajuan&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Status&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;MENUNGGU&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;assertIn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;status&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;exception&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;detail&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;assertEqual&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;fake_notifier&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;calls&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_illegal_transition_disetujui_to_menunggu_raises_validation_error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="c1"&gt;# CFG: DISETUJUI→MENUNGGU (backward)
&lt;/span&gt;    &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;pengajuan&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Pengajuan&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Status&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DISETUJUI&lt;/span&gt;
    &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;pengajuan&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;save&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;update_fields&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;status&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
    &lt;span class="n"&gt;service&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;_build_service&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;assertRaises&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ValidationError&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;service&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;update_status&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;pengajuan&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Pengajuan&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Status&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;MENUNGGU&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;assertIn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;status&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;exception&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;detail&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;assertEqual&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;fake_notifier&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;calls&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A total of 5 new CFG tests for illegal transitions were added along with annotations on the existing tests.&lt;/p&gt;




&lt;h3&gt;
  
  
  Mutation Testing for Developer Confidence
&lt;/h3&gt;

&lt;p&gt;We realized that achieving 100% line coverage can sometimes be a vanity metric if the assertions evaluating that code are weak. To make our TDD process more rigorous and genuinely helpful for developers, we integrated Mutation Testing algorithms.&lt;/p&gt;

&lt;p&gt;Mutation testing forces the insertion of small, artificial bugs ("Mutants") into the original code. If a mutant survives the test suite, it means the test case is weak. By actively hunting down these mutants, we made our testing suite bulletproof.&lt;/p&gt;

&lt;h4&gt;
  
  
  A Note on Engineering Pragmatism: Setting the 80% Threshold
&lt;/h4&gt;

&lt;p&gt;We deliberately established an &lt;strong&gt;80% mutation score&lt;/strong&gt; as our baseline threshold for this project. While achieving a 100% score is an ideal, practical software engineering requires balancing test confidence with delivery speed. Reaching this &amp;gt;80% mark provides strong confidence that our core business logic and API contracts are well-protected.&lt;/p&gt;

&lt;p&gt;We carefully review the remaining survivors and accept them only when they are equivalent, non-critical, or have low business impact. For example, Stryker generated a mutant that removed the &lt;code&gt;.trim()&lt;/code&gt; method from our empty-field validation (&lt;code&gt;formData.email.trim() !== ""&lt;/code&gt;).&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1dps2cnfok0ext1vgdhg.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1dps2cnfok0ext1vgdhg.png" alt=" " width="800" height="283"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;While technically a logic change, writing a highly specific test case to input blank spaces (&lt;code&gt;" "&lt;/code&gt;) just to kill this mutant is unnecessary, as invalid formats are ultimately caught by subsequent regex and strict backend validations. Accepting these specific survivors ensures our test suite remains highly effective without yielding to over-engineering.&lt;/p&gt;

&lt;h4&gt;
  
  
  A. Backend Side Using mutmut
&lt;/h4&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fidz3mslxs8opb8h28pwy.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fidz3mslxs8opb8h28pwy.png" alt=" " width="800" height="237"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2oe8kg2zbmjub37d1su5.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2oe8kg2zbmjub37d1su5.png" alt=" " width="800" height="455"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;To test our backend, we used mutmut. Initially, the tool generated &lt;strong&gt;40 mutants&lt;/strong&gt; across our codebase. Our original test suite managed to kill 31 of them, leaving 9 survivors and resulting in a baseline mutation score of &lt;strong&gt;77.5%&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;By analyzing these survivors, we realized our assertions weren't strict enough. As seen in the report above, a mutant successfully survived by altering a response dictionary key named &lt;code&gt;"detail"&lt;/code&gt; to &lt;code&gt;"XXdetailXX"&lt;/code&gt;. We killed that mutant by updating our test to explicitly require &lt;code&gt;self.assertIn("detail", response.data)&lt;/code&gt; in the "Submission not found" scenario. After these targeted improvements, we successfully killed 33 mutants, reducing the survivors to just 7 and bumping our final mutation score to &lt;strong&gt;82.5%&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8i993jgpnso005sd7hvb.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8i993jgpnso005sd7hvb.png" alt=" " width="800" height="246"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  B. Frontend Side Using Stryker Mutator
&lt;/h4&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fu9kzxps9iduxgzsbyhjd.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fu9kzxps9iduxgzsbyhjd.png" alt=" " width="800" height="322"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;On the frontend side, we utilized Stryker Mutator. Because of the complex UI logic, the tool generated a massive &lt;strong&gt;280 mutants&lt;/strong&gt;. Our comprehensive frontend test suite successfully tracked down and killed 223 of them. With only 55 mutants surviving, we achieved a strong mutation score of &lt;strong&gt;80.29%&lt;/strong&gt;, proving that our frontend tests are highly resilient against unexpected logic changes.&lt;/p&gt;




&lt;h2&gt;
  
  
  Benefits, Concrete Data, and Best Practices
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Quantitative Data
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Metric&lt;/th&gt;
&lt;th&gt;Before&lt;/th&gt;
&lt;th&gt;After&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Backend mutation score with mutmut&lt;/td&gt;
&lt;td&gt;31/40 killed, 9 survived = 77.5%&lt;/td&gt;
&lt;td&gt;33/40 killed, 7 survived = 82.5%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Frontend mutation score with Stryker&lt;/td&gt;
&lt;td&gt;threshold set at 80%&lt;/td&gt;
&lt;td&gt;223/280 killed, 55 survived = 80.29%, passing the threshold&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;mutmut scope (paths_to_mutate)&lt;/td&gt;
&lt;td&gt;4 paths (views only)&lt;/td&gt;
&lt;td&gt;9 paths (views, services, serializers)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Stryker threshold FE&lt;/td&gt;
&lt;td&gt;none&lt;/td&gt;
&lt;td&gt;break 70, high 80&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Documented ISP partitions (BE)&lt;/td&gt;
&lt;td&gt;~5 (implicit)&lt;/td&gt;
&lt;td&gt;28 explicit and annotated&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;CFG paths covered (StatusChangeService)&lt;/td&gt;
&lt;td&gt;2 to 3 legal&lt;/td&gt;
&lt;td&gt;4 legal and 5 illegal&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  Connection to Literature
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;ISP by Ammann &amp;amp; Offutt (2016)&lt;/strong&gt;&lt;br&gt;
Ammann and Offutt define Input Space Partitioning as the division of an input domain into partitions where each must be represented by at least one test. The base-choice coverage strategy I used is their recommendation for balancing coverage with efficiency.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Mutation Testing by Jia &amp;amp; Harman (2011)&lt;/strong&gt;&lt;br&gt;
The survey by Jia and Harman shows that the mutation score is a more reliable predictor of test suite quality than statement coverage. They also documented that equivalent mutants and low-value survivors are major practical challenges. In this project, I reviewed surviving mutants instead of blindly chasing a 100% score; for example, the Stryker &lt;code&gt;.trim()&lt;/code&gt; survivor was accepted because stricter backend validation already rejects the same invalid input class.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Petrović &amp;amp; Ivanković (ICSE 2021) on Mutation Testing&lt;/strong&gt;&lt;br&gt;
This paper reports the results of deploying mutation testing at scale at Google. Developers who receive mutation testing feedback consistently write better tests. The Stryker threshold follows this principle by turning mutation score into an explicit project-level quality threshold instead of leaving it as an optional report.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Meszaros, xUnit Test Patterns (2007)&lt;/strong&gt;&lt;br&gt;
Test smells such as Assertion Roulette (multiple assertions without messages) and Obscure Test (tests that are difficult to understand) are anti-patterns I avoid. Every new test has one clear assertion and an ISP or CFG comment.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Google Testing Blog on Mutation Testing at Google (2018)&lt;/strong&gt;&lt;br&gt;
Google recommends focusing on killed mutants per time rather than the raw mutation score. This means prioritizing mutants in frequently changing code, which aligns with the auth and pengajuan features in this sprint.&lt;/p&gt;

&lt;h3&gt;
  
  
  Best Practices Followed
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Each-choice minimum with base-choice for crucial parameters&lt;/strong&gt; (Ammann &amp;amp; Offutt recommendation)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Mutation score as a project quality threshold rather than a vanity metric&lt;/strong&gt; (Google engineering practice)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Test isolation via &lt;code&gt;override_settings&lt;/code&gt; and &lt;code&gt;locmem&lt;/code&gt; cache&lt;/strong&gt; for rate limiter tests to avoid flakiness&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Annotation-based traceability&lt;/strong&gt; (&lt;code&gt;# ISP&lt;/code&gt;, &lt;code&gt;# CFG&lt;/code&gt;) to allow coverage auditing without reading all the code&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  Critique of Previous Testing and Measurable Improvements
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Anti-Patterns Found in the Old Test Suite
&lt;/h3&gt;

&lt;h4&gt;
  
  
  Anti-Pattern 1 Happy-Path-Only Register Serializer Test
&lt;/h4&gt;

&lt;p&gt;&lt;strong&gt;Location&lt;/strong&gt; &lt;code&gt;authentication/tests/test_register_serializers.py&lt;/code&gt; (before this sprint)&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Problem&lt;/strong&gt; The registration test only verified that valid data passed the serializer. There were no tests for the following scenarios.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Passwords with a length of exactly 7 (boundary condition that should fail) versus exactly 8 (should pass)&lt;/li&gt;
&lt;li&gt;Emails that are already registered but not yet activated (which behave differently from active ones)&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;ADMIN&lt;/code&gt; role which should not be able to self-register&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Why this is weak&lt;/strong&gt; A mutant changing &lt;code&gt;len(password) &amp;lt; 8&lt;/code&gt; to &lt;code&gt;len(password) &amp;lt;= 8&lt;/code&gt; or &lt;code&gt;len(password) &amp;lt; 7&lt;/code&gt; would survive because no test could distinguish the difference. This is a classic Boundary Value Analysis gap based on Myers' &lt;em&gt;The Art of Software Testing&lt;/em&gt; (1979).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Fix&lt;/strong&gt; Added &lt;code&gt;test_serializer_rejects_password_exactly_7_chars&lt;/code&gt;, &lt;code&gt;test_serializer_rejects_email_already_registered_inactive&lt;/code&gt;, and &lt;code&gt;test_serializer_rejects_admin_registration&lt;/code&gt; with &lt;code&gt;# ISP&lt;/code&gt; annotations for traceability.&lt;/p&gt;

&lt;h4&gt;
  
  
  Anti-Pattern 2 StatusChangeService Did Not Test Illegal Transitions
&lt;/h4&gt;

&lt;p&gt;&lt;strong&gt;Location&lt;/strong&gt; &lt;code&gt;pengajuan/tests/test_services.py&lt;/code&gt; (before this sprint)&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Problem&lt;/strong&gt; There were only tests for legal transitions (MENUNGGU to DISETUJUI, and MENUNGGU to DITOLAK). There were no tests verifying that backward transitions (DISETUJUI to MENUNGGU) or self-loops (MENUNGGU to MENUNGGU) raise an exception.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why this is weak&lt;/strong&gt; A mutant removing one condition in the &lt;code&gt;ALLOWED_TRANSITIONS&lt;/code&gt; dictionary would survive. A state machine that is not tested exhaustively could allow status transitions that corrupt data integrity.&lt;/p&gt;

&lt;p&gt;Following the principles from Meszaros (&lt;em&gt;xUnit Test Patterns&lt;/em&gt;), tests must verify error behavior just as rigorously as happy behavior.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Fix&lt;/strong&gt; Added 5 CFG tests for illegal transitions with assertions that ensure exceptions are raised.&lt;/p&gt;

&lt;h4&gt;
  
  
  Anti-Pattern 3 Frontend Tests Did Not Cover API Error Variants
&lt;/h4&gt;

&lt;p&gt;&lt;strong&gt;Location&lt;/strong&gt; &lt;code&gt;tests/features/authentication/components/RegisterForm.test.tsx&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Problem&lt;/strong&gt; The registration form tests only mocked the success scenario (201) and generic errors. There were no tests for the following situations.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;HTTP 429 rate limit response which should display a throttling message&lt;/li&gt;
&lt;li&gt;HTTP 400 with field-specific errors which should map to the correct fields&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Why this is weak&lt;/strong&gt; A Stryker mutant changing the HTTP status check condition would survive. This is also an Over-Mocked Service smell according to Meszaros, as overly generic mocks do not exercise real branch logic.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Fix&lt;/strong&gt; Added the RegisterForm 429 rate-limit test with the annotation &lt;code&gt;// ISP: ApiError.status.429&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Measurable Improvements (Before and After)
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Dimension&lt;/th&gt;
&lt;th&gt;Before&lt;/th&gt;
&lt;th&gt;After&lt;/th&gt;
&lt;th&gt;Delta&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Annotated ISP partitions&lt;/td&gt;
&lt;td&gt;0 (implicit)&lt;/td&gt;
&lt;td&gt;28 (explicit)&lt;/td&gt;
&lt;td&gt;+28&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Covered CFG illegal transitions&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;+5&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Mutmut scope (auth and pengajuan)&lt;/td&gt;
&lt;td&gt;0 paths&lt;/td&gt;
&lt;td&gt;5 new paths&lt;/td&gt;
&lt;td&gt;baseline capture enabled&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Backend mutation score&lt;/td&gt;
&lt;td&gt;77.5%&lt;/td&gt;
&lt;td&gt;82.5%&lt;/td&gt;
&lt;td&gt;+5 percentage points&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Frontend mutation score&lt;/td&gt;
&lt;td&gt;80% threshold&lt;/td&gt;
&lt;td&gt;80.29%&lt;/td&gt;
&lt;td&gt;passed threshold&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Stryker threshold FE&lt;/td&gt;
&lt;td&gt;none&lt;/td&gt;
&lt;td&gt;break 70, high 80&lt;/td&gt;
&lt;td&gt;configured project threshold&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  Connection to Industry Standards
&lt;/h3&gt;

&lt;p&gt;Google researchers (Petrović et al., ICSE 2021) found that mutation testing is most effective when integrated into the developer workflow as automated feedback rather than just a final report. The Stryker threshold I set implements this pattern by giving the team a concrete mutation-score threshold to enforce when mutation testing is run.&lt;/p&gt;

&lt;p&gt;The Stryker Mutator whitepaper (2023) recommends setting &lt;code&gt;coverageAnalysis&lt;/code&gt; to &lt;code&gt;"perTest"&lt;/code&gt; (which is already active in the config) to isolate mutants to the specific tests that cover them. This reduces false positives and execution time.&lt;/p&gt;




&lt;h2&gt;
  
  
  Commit Links
&lt;/h2&gt;

&lt;h3&gt;
  
  
  BE-GBM MR !158
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Commit&lt;/th&gt;
&lt;th&gt;Message&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;b7564fc5&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;chore(testing): expand mutmut scope to authentication and pengajuan services&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;0c774595&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;[GREEN] test(auth): add ISP partitions for register serializer, activation, and admin verification&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;a2def037&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;[GREEN] test(pengajuan): add CFG prime path coverage for StatusChangeService state machine&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;MR Link:&lt;/strong&gt; &lt;a href="https://gitlab.cs.ui.ac.id/ppl-fasilkom-ui/2026/kelas-d/group1-gb/be-gbm/-/merge_requests/158" rel="noopener noreferrer"&gt;https://gitlab.cs.ui.ac.id/ppl-fasilkom-ui/2026/kelas-d/group1-gb/be-gbm/-/merge_requests/158&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  fe-gbm MR !142
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Commit&lt;/th&gt;
&lt;th&gt;Message&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;f18590fe&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;chore(testing): enforce Stryker mutation threshold at 80% high and 70% break&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;42d9b028&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;[GREEN] test(auth): add ISP partitions for RegisterForm and useActivation hook&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;19328728&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;[GREEN] test(pengajuan): add CFG branch coverage for useUpdateStatusPengajuan&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;MR Link:&lt;/strong&gt; &lt;a href="https://gitlab.cs.ui.ac.id/ppl-fasilkom-ui/2026/kelas-d/group1-gb/fe-gbm/-/merge_requests/142" rel="noopener noreferrer"&gt;https://gitlab.cs.ui.ac.id/ppl-fasilkom-ui/2026/kelas-d/group1-gb/fe-gbm/-/merge_requests/142&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Ammann, P. &amp;amp; Offutt, J. (2016). &lt;em&gt;Introduction to Software Testing&lt;/em&gt; (2nd ed.). Cambridge University Press. (ISP ch.6, Graph Coverage ch.7).&lt;/li&gt;
&lt;li&gt;Jia, Y. &amp;amp; Harman, M. (2011). "An Analysis and Survey of the Development of Mutation Testing." &lt;em&gt;IEEE Transactions on Software Engineering&lt;/em&gt;, 37(5), 649-678.&lt;/li&gt;
&lt;li&gt;Petrović, G., Ivanković, M., Fraser, G., &amp;amp; Just, R. (2021). "Does Mutation Testing Improve Testing Practices?" &lt;em&gt;ICSE 2021&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;Petrović, G. &amp;amp; Ivanković, M. (2018). "State of Mutation Testing at Google." &lt;em&gt;Google Testing Blog&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;Meszaros, G. (2007). &lt;em&gt;xUnit Test Patterns Refactoring Test Code&lt;/em&gt;. Addison-Wesley.&lt;/li&gt;
&lt;li&gt;Stryker Mutator. (2023). "Mutation Testing in Practice." &lt;em&gt;stryker-mutator.io&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;Myers, G.J. (1979). &lt;em&gt;The Art of Software Testing&lt;/em&gt;. Wiley. (Boundary Value Analysis).&lt;/li&gt;
&lt;/ol&gt;

</description>
      <category>cicd</category>
      <category>computerscience</category>
      <category>softwareengineering</category>
      <category>testing</category>
    </item>
  </channel>
</rss>
