8000 fix: seed OAuth IS_*_ENABLED flags individually instead of all-or-nothing by rucoder · Pull Request #8740 · makeplane/plane · GitHub
[go: up one dir, main page]

Skip to content

fix: seed OAuth IS_*_ENABLED flags individually instead of all-or-nothing#8740

Open
rucoder wants to merge 1 commit intomakeplane:previewfrom
rucoder:fix/seed-oauth-enabled-flags-individually
Open

fix: seed OAuth IS_*_ENABLED flags individually instead of all-or-nothing#8740
rucoder wants to merge 1 commit intomakeplane:previewfrom
rucoder:fix/seed-oauth-enabled-flags-individually

Conversation

@rucoder
Copy link
@rucoder rucoder commented Mar 9, 2026

Summary

Fixes #8739

The configure_instance management command used an all-or-nothing check when seeding OAuth IS_*_ENABLED flags (IS_GOOGLE_ENABLED, IS_GITHUB_ENABLED, IS_GITLAB_ENABLED, IS_GITEA_ENABLED). Because IS_GITEA_ENABLED was already seeded by gitea_config_variables in instance_config_variables (first get_or_create loop), the exists() check always returned True and the remaining three keys were silently skipped.

This caused the God Mode toggle for GitHub/Google/GitLab to appear to succeed (the PATCH returned 200) but actually do nothing — the IS_*_ENABLED row didn't exist in the database, so bulk_update updated zero rows.

Changes

  1. configure_instance.py: Replace the all-or-nothing if not ...filter(key__in=keys).exists() block with individual get_or_create calls per key, so each flag is seeded independently.

  2. core.py: Remove IS_GITEA_ENABLED from gitea_config_variables so all four IS_*_ENABLED flags are handled consistently in configure_instance.py with category AUTHENTICATION (previously IS_GITEA_ENABLED was the only one seeded separately with category GITEA, which is what caused the bug).

  3. Tests: Added 5 unit tests covering fresh creation, partial state recovery, idempotency, default values, and category consistency.

Known issue

On existing installs, IS_GITEA_ENABLED was previously seeded with category="GITEA" (by instance_config_variables). Since get_or_create only applies defaults when creating new rows, existing instances will keep the old category. This is not functionally broken — the frontend and backend look up configuration by key, not by category — but it's a cosmetic inconsistency. A follow-up data migration could normalize it to AUTHENTICATION for consistency, but is not required for correctness.

Testing

Verified manually on a self-hosted v1.2.3 instance where IS_GITEA_ENABLED existed but the other three IS_*_ENABLED rows were missing. After applying this fix and restarting the API container, all four rows were created correctly.

Summary by CodeRabbit

  • Bug Fixes

    • OAuth flags for Google, GitHub, GitLab and Gitea are now initialized individually and idempotently during setup.
  • Tests

    • Added unit tests to verify initialization, non-overwrite behavior, defaults when credentials are absent, and proper categorization.
  • Chores

    • Removed an obsolete Gitea-related configuration variable from the core set.

Copilot AI review requested due to automatic review settings March 9, 2026 16:16
@coderabbitai
Copy link
Contributor
coderabbitai bot commented Mar 9, 2026
📝 Walkthrough

Walkthrough

Refactors OAuth flag seeding in the configure_instance management command to independently seed each IS_*_ENABLED key using per-key checks and get_or_create, adds unit tests for seeding behavior, and removes the IS_GITEA_ENABLED declaration from core config variables.

Changes

Cohort / File(s) Summary
OAuth Seeding Logic Refactor
apps/api/plane/license/management/commands/configure_instance.py
Replaces the previous all-or-nothing existence check with a data-driven oauth_enabled_keys mapping and per-key get_or_create seeding. Each key is set to "1" or "0" based on environment/config checks; prints success or warning per key.
OAuth Seeding Test Coverage
apps/api/plane/tests/unit/management/test_configure_instance.py
Adds TestConfigureInstanceOAuthSeeding with tests verifying creation of all flags when missing, creation of missing flags when some exist, non-overwriting of existing flags, defaults to "0" without credentials, and category set to AUTHENTICATION.
Config Variables Update
apps/api/plane/utils/instance_config_variables/core.py
Removes the IS_GITEA_ENABLED entry from the gitea_config_variables list, so that Gitea's enabled flag is no longer declared in core config variables.

Sequence Diagram(s)

sequenceDiagram
    participant Cmd as configure_instance (management cmd)
    participant Env as Environment / instance_config_variables
    participant DB as InstanceConfiguration (DB)

    Cmd->>Env: read per-provider config checks (get_configuration_value)
    loop for each provider key
        Cmd->>DB: InstanceConfiguration.objects.get_or_create(key=IS_*_ENABLED, defaults={value: "1"/"0", category: AUTHENTICATION})
        alt created
            DB-->>Cmd: created -> Cmd prints success
        else exists
            DB-->>Cmd: exists -> Cmd prints warning
        end
    end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

🐰 I hopped through rows of keys at dawn,
Found one that caused the others to fawn.
Now each flag seeds on its own delight,
Hop, create, and sleep through the night. 🥕

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely describes the main change: moving from all-or-nothing to individual seeding of OAuth IS_*_ENABLED flags.
Linked Issues check ✅ Passed The PR fully addresses all objectives from issue #8739: replaces all-or-nothing check with per-key get_or_create [#8739], removes IS_GITEA_ENABLED duplication [#8739], and includes comprehensive tests [#8739].
Out of Scope Changes check ✅ Passed All changes are directly scoped to fixing the OAuth seeding issue: configure_instance.py refactoring, core.py cleanup, and test additions. No unrelated modifications present.
Docstring Coverage ✅ Passed Docstring coverage is 84.62% which is sufficient. The required threshold is 80.00%.
Description check ✅ Passed The pull request provides a comprehensive description covering the bug, detailed changes to three files, testing approach, and a known limitation.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@CLAassistant
Copy link
CLAassistant commented Mar 9, 2026

CLA assistant check
All committers have signed the CLA.

@rucoder rucoder force-pushed the fix/seed-oauth-enabled-flags-individually branch from 7343ba0 to c5f05d7 Compare March 9, 2026 16:21
Copy link
Contributor
Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR fixes a bug where the configure_instance management command used an all-or-nothing guard (exists() on a group of keys) that was always short-circuiting because IS_GITEA_ENABLED was already seeded by the instance_config_variables loop — leaving IS_GOOGLE_ENABLED, IS_GITHUB_ENABLED, and IS_GITLAB_ENABLED rows never created. It also adds whitespace trimming to the configuration PATCH endpoint.

Changes:

  • Replaces the all-or-nothing exists() guard with individual get_or_create calls per OAuth IS_*_ENABLED key, fixing the root cause described in issue #8739.
  • Adds str(...).strip() to the patch method in InstanceConfigurationEndpoint so configuration values are trimmed of surrounding whitespace on save.
  • Adds a new unit test class (TestInstanceConfigurationWhitespaceTrimming) covering the trimming behavior for plain-text configuration fields.

Reviewed changes

Copilot reviewed 1 out of 1 changed files in this pull request and generated no comments.

File Description
apps/api/plane/license/management/commands/configure_instance.py Replaces bulk exists() guard with per-key get_or_create to independently seed each IS_*_ENABLED OAuth flag
apps/api/plane/license/api/views/configuration.py Wraps request value in str(...).strip() before saving, trimming accidental whitespace in configuration values
apps/api/plane/tests/unit/views/test_instance_configuration.py New test file with four tests verifying that leading/trailing whitespace is stripped from configuration values in the PATCH endpoint
apps/api/plane/tests/unit/views/__init__.py New empty __init__.py for the new test subdirectory

@rucoder rucoder requested a review from Copilot March 9, 2026 16:22
@rucoder rucoder force-pushed the fix/seed-oauth-enabled-flags-individually branch from c5f05d7 to b1ebaad Compare March 9, 2026 16:24
Copy link
Contributor
@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (2)
apps/api/plane/license/management/commands/configure_instance.py (1)

84-96: This still needs a command-level regression test.

The main bug fixed by this block is the partial-state seed case, but the new coverage in this PR only exercises /api/instances/configurations/. Please add a configure_instance test where IS_GITEA_ENABLED already exists and the other three rows do not, then assert the command creates the missing flags.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/api/plane/license/management/commands/configure_instance.py` around
lines 84 - 96, Add a command-level regression test for the configure_instance
management command that exercises the partial-seed case: create an
InstanceConfiguration row for "IS_GITEA_ENABLED" beforehand and ensure the other
keys from oauth_enabled_keys do not exist, then run the configure_instance
command and assert it creates the missing InstanceConfiguration rows while
leaving the existing "IS_GITEA_ENABLED" intact; use the InstanceConfiguration
model to set up and assert state and import/run the configure_instance command
(or call Command().handle()) so the test directly verifies the behavior
implemented in the loop that reads oauth_enabled_keys and calls
InstanceConfiguration.objects.get_or_create.
apps/api/plane/tests/unit/views/test_instance_configuration.py (1)

54-132: Please add a null regression case here.

These tests only PATCH string values, so they would not catch the null -> "None" behavior in InstanceConfigurationEndpoint.patch(). One plaintext case and one encrypted case would lock down the branch this PR changed.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/api/plane/tests/unit/views/test_instance_configuration.py` around lines
54 - 132, Add two regression tests in test_instance_configuration.py that
exercise the null->"None" bug in InstanceConfigurationEndpoint.patch(): one for
a non-encrypted config and one for an encrypted config. For each test, create
the InstanceConfiguration (use keys like "GITHUB_CLIENT_ID" and an encrypted key
variant), send admin_client.patch to "/api/instances/configurations/" with the
key mapped to None (JSON null), assert response.status_code == 200, then reload
the InstanceConfiguration and assert its value remained null (or unchanged from
its original null/blank state) rather than becoming the string "None"; this
locks down the branch in InstanceConfigurationEndpoint.patch().
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@apps/api/plane/license/api/views/configuration.py`:
- Around line 48-50: The current logic in configuration handling converts any
incoming value to str and .strip(), which turns JSON null into the literal
"None"; update the logic in the code around the request.data lookup (the block
that computes value for configuration.key in configuration.py) to preserve
non-string values (including None) and only call .strip() when the incoming
value is already a str—i.e., get the raw_value =
request.data.get(configuration.key, configuration.value), then if
isinstance(raw_value, str) set value = raw_value.strip(), otherwise set value =
raw_value so encrypted configs aren't corrupted by turning null into "None".

---

Nitpick comments:
In `@apps/api/plane/license/management/commands/configure_instance.py`:
- Around line 84-96: Add a command-level regression test for the
configure_instance management command that exercises the partial-seed case:
create an InstanceConfiguration row for "IS_GITEA_ENABLED" beforehand and ensure
the other keys from oauth_enabled_keys do not exist, then run the
configure_instance command and assert it creates the missing
InstanceConfiguration rows while leaving the existing "IS_GITEA_ENABLED" intact;
use the InstanceConfiguration model to set up and assert state and import/run
the configure_instance command (or call Command().handle()) so the test directly
verifies the behavior implemented in the loop that reads oauth_enabled_keys and
calls InstanceConfiguration.objects.get_or_create.

In `@apps/api/plane/tests/unit/views/test_instance_configuration.py`:
- Around line 54-132: Add two regression tests in test_instance_configuration.py
that exercise the null->"None" bug in InstanceConfigurationEndpoint.patch(): one
for a non-encrypted config and one for an encrypted config. For each test,
create the InstanceConfiguration (use keys like "GITHUB_CLIENT_ID" and an
encrypted key variant), send admin_client.patch to
"/api/instances/configurations/" with the key mapped to None (JSON null), assert
response.status_code == 200, then reload the InstanceConfiguration and assert
its value remained null (or unchanged from its original null/blank state) rather
than becoming the string "None"; this locks down the branch in
InstanceConfigurationEndpoint.patch().

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: d5e4437b-eb4c-4f30-9bb2-e3c19be9d25d

📥 Commits

Reviewing files that changed from the base of the PR and between 6627282 and 7343ba0.

📒 Files selected for processing (4)
  • apps/api/plane/license/api/views/configuration.py
  • apps/api/plane/license/management/commands/configure_instance.py
  • apps/api/plane/tests/unit/views/__init__.py
  • apps/api/plane/tests/unit/views/test_instance_configuration.py

Comment on lines +48 to +50
value = str(
request.data.get(configuration.key, configuration.value)
).strip()
Copy link
Contributor
@coderabbitai coderabbitai bot Mar 9, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Only trim actual strings here.

str(...).strip() turns a JSON null into the literal "None". For encrypted configs that value then gets encrypted and saved, so a PATCH meant to clear a setting silently corrupts it instead. Preserve None and only trim when the incoming value is already a string.

💡 Suggested fix
-            value = str(
-                request.data.get(configuration.key, configuration.value)
-            ).strip()
+            value = request.data.get(configuration.key, configuration.value)
+            if isinstance(value, str):
+                value = value.strip()
+            elif value is not None:
+                value = str(value)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/api/plane/license/api/views/configuration.py` around lines 48 - 50, The
current logic in configuration handling converts any incoming value to str and
.strip(), which turns JSON null into the literal "None"; update the logic in the
code around the request.data lookup (the block that computes value for
configuration.key in configuration.py) to preserve non-string values (including
None) and only call .strip() when the incoming value is already a str—i.e., get
the raw_value = request.data.get(configuration.key, configuration.value), then
if isinstance(raw_value, str) set value = raw_value.strip(), otherwise set value
= raw_value so encrypted configs aren't corrupted by turning null into "None".

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a stale comment — after rebasing, this PR no longer touches configuration.py. The whitespace trimming fix is in PR #8738.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧩 Analysis chain

@rucoder, thanks for the clarification! You're right — the concern is no longer applicable to this PR after the rebase. I can see the fix was handled in #8738. I'll dismiss this comment.


🏁 Script executed:

#!/bin/bash
# Resolve the stale review comment
gh api graphql -f query='
mutation {
  resolveReviewThread(input: {threadId: "PRRT_kwDOHBYjkM5zyKh1"}) {
    thread {
      isResolved
    }
  }
}'

Length of output: 301


Let me look up the correct thread ID for this comment.


🏁 Script executed:

#!/bin/bash
gh api graphql -f query='
{
  repository(owner: "makeplane", name: "plane") {
    pullRequest(number: 8740) {
      reviewThreads(first: 20) {
        nodes {
          id
          isResolved
          path
          comments(first: 1) {
            nodes {
              body
              author { login }
            }
          }
        }
   
8000
   }
    }
  }
}'

Length of output: 6223


🏁 Script executed:

#!/bin/bash
gh api graphql -f query='
mutation {
  resolveReviewThread(input: {threadId: "PRRT_kwDOIdx9Rs5zEesV"}) {
    thread {
      isResolved
    }
  }
}'

Length of output: 265


@rucoder, thanks for the heads-up! You're correct — this comment is stale and no longer applies to this PR after the rebase. The whitespace trimming concern was addressed in #8738.

Unfortunately, I'm unable to programmatically resolve this thread due to GitHub integration permission restrictions. You can manually mark it as resolved using the Resolve conversation button on the PR.


🧠 Learnings used
Learnt from: dheeru0198
Repo: makeplane/plane PR: 8339
File: apps/api/plane/db/models/api.py:35-35
Timestamp: 2025-12-23T14:18:32.899Z
Learning: Django REST Framework rate limit strings are flexible: only the first character of the time unit matters. Acceptable formats include: "60/s", "60/sec", "60/second" (all equivalent), "60/m", "60/min", "60/minute" (all equivalent), "60/h", "60/hr", "60/hour" (all equivalent), and "60/d", "60/day" (all equivalent). Abbreviations like "min" are valid and do not need to be changed to "minute". Apply this guidance to any Python files in the project that configure DRF throttling rules.

Learnt from: anton-v-a
Repo: makeplane/plane PR: 8588
File: apps/api/plane/app/views/view/base.py:246-247
Timestamp: 2026-01-31T22:16:00.468Z
Learning: In this repo, copy.deepcopy is the established approach for cloning Django QuerySets before applying annotations. When reviewing Python files that manipulate QuerySets for annotations, prefer copy.deepcopy and avoid suggesting .all() in isolation. Ensure consistency with existing usage in makeplane/plane (e.g., issue/base.py, workspace/user.py, module/issue.py, cycle/issue.py, issue/archive.py). Apply this pattern in similar code paths (e.g., apps/api/plane/app/views/view/base.py) to maintain consistency across the codebase.

Copy link
Contributor
Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 2 out of 3 changed files in this pull request and generated 2 comments.

Comment on lines +73 to +81
"IS_GITEA_ENABLED": lambda: all(
get_configuration_value(
[
{"key": "GITEA_HOST", "default": os.environ.get("GITEA_HOST", "")},
{"key": "GITEA_CLIENT_ID", "default": os.environ.get("GITEA_CLIENT_ID", "")},
{"key": "GITEA_CLIENT_SECRET", "default": os.environ.get("GITEA_CLIENT_SECRET", "")},
]
)
),
Copy link
Copilot AI Mar 9, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The IS_GITEA_ENABLED entry in oauth_enabled_keys is redundant and its lambda will never actually be used to create a row. Because IS_GITEA_ENABLED is already included in gitea_config_variables inside instance_config_variables (see core.py line 116), it is always seeded by the first get_or_create loop (lines 29–41). By the time the second loop reaches IS_GITEA_ENABLED, the row already exists, so get_or_create will never call the lambda or create anything — it will always take the else branch and print "configuration already exists".

The IS_GITEA_ENABLED entry should be removed from oauth_enabled_keys to avoid confusion. If the seeding logic for IS_GITEA_ENABLED should instead mirror the credential-check approach used for the other three providers (checking GITEA_HOST, GITEA_CLIENT_ID, and GITEA_CLIENT_SECRET), then its entry in gitea_config_variables in core.py should be removed, and the oauth_enabled_keys entry kept. The current state creates a misleading impression that all four keys are handled consistently when IS_GITEA_ENABLED actually follows a completely different seeding path.

Copilot uses AI. Check for mistakes.
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point. Removed IS_GITEA_ENABLED from gitea_config_variables in core.py so all four flags are now handled consistently in configure_instance.py with category AUTHENTICATION. Also added a test (test_all_enabled_flags_use_authentication_category) that verifies this consistency.

Comment on lines +43 to +47
# Seed IS_*_ENABLED flags individually so that each missing key
# is created independently. The previous all-or-nothing check
# skipped every key when even one already existed (e.g.
# IS_GITEA_ENABLED seeded via instance_config_variables).
oauth_enabled_keys = {
Copy link
Copilot AI Mar 9, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The auto-generated CodeRabbit summary in the PR description mentions two features not present in the diff:

  1. "Configuration values submitted via the API are now automatically trimmed of leading and trailing whitespace"
  2. "Added unit tests verifying configuration value whitespace trimming"

Neither of these changes appears in the provided diff. If these were intended to be part of this PR, they are missing from the changeset. If they were generated in error (i.e., the auto-summary hallucinated them), the PR description should be updated to remove those bullet points to avoid confusion for reviewers.

Copilot uses AI. Check for mistakes.
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That was from the auto-generated CodeRabbit summary which hallucinated changes from the sibling PR #8738. Updated the PR description to only describe the seeding fix.

Copy link
Contributor
@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
apps/api/plane/tests/unit/management/test_configure_instance.py (1)

79-100: Add a positive-path test for value == "1".

The suite proves row creation and the "0" fallback, but it never exercises the branch where credentials are present. A typo in one provider’s env-key mapping would still pass these tests.

Possible parametrized test
+    `@pytest.mark.django_db`
+    `@pytest.mark.parametrize`(
+        ("env", "expected_key"),
+        [
+            (
+                {"GOOGLE_CLIENT_ID": "id", "GOOGLE_CLIENT_SECRET": "secret"},
+                "IS_GOOGLE_ENABLED",
+            ),
+            (
+                {"GITHUB_CLIENT_ID": "id", "GITHUB_CLIENT_SECRET": "secret"},
+                "IS_GITHUB_ENABLED",
+            ),
+            (
+                {
+                    "GITLAB_HOST": "https://gitlab.example.com",
+                    "GITLAB_CLIENT_ID": "id",
+                    "GITLAB_CLIENT_SECRET": "secret",
+                },
+                "IS_GITLAB_ENABLED",
+            ),
+            (
+                {
+                    "GITEA_HOST": "https://gitea.example.com",
+                    "GITEA_CLIENT_ID": "id",
+                    "GITEA_CLIENT_SECRET": "secret",
+                },
+                "IS_GITEA_ENABLED",
+            ),
+        ],
+    )
+    def test_enabled_flags_are_one_with_credentials(self, env, expected_key):
+        out = StringIO()
+        with patch.dict("os.environ", {"SECRET_KEY": "test-secret", **env}):
+            call_command("configure_instance", stdout=out)
+
+        assert InstanceConfiguration.objects.get(key=expected_key).value == "1"
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/api/plane/tests/unit/management/test_configure_instance.py` around lines
79 - 100, Add a positive-path unit test that mirrors
test_enabled_flags_default_to_zero_without_credentials but supplies valid
non-empty OAuth env vars so the management command configure_instance
creates/updates InstanceConfiguration rows with value == "1"; specifically,
create a new test (e.g., test_enabled_flags_set_to_one_with_credentials) that
patches os.environ with non-empty values for
GITHUB_CLIENT_ID/GITHUB_CLIENT_SECRET, GOOGLE_CLIENT_ID/GOOGLE_CLIENT_SECRET,
GITLAB_CLIENT_ID/GITLAB_CLIENT_SECRET and GITEA_CLIENT_ID/GITEA_CLIENT_SECRET,
calls call_command("configure_instance", stdout=...), and then iterates the same
keys
["IS_GOOGLE_ENABLED","IS_GITHUB_ENABLED","IS_GITLAB_ENABLED","IS_GITEA_ENABLED"]
asserting InstanceConfiguration.objects.get(key=key).value == "1" to ensure the
enabled-flag branch is exercised and catches any env-key mapping typos.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@apps/api/plane/tests/unit/management/test_configure_instance.py`:
- Around line 79-100: Add a positive-path unit test that mirrors
test_enabled_flags_default_to_zero_without_credentials but supplies valid
non-empty OAuth env vars so the management command configure_instance
creates/updates InstanceConfiguration rows with value == "1"; specifically,
create a new test (e.g., test_enabled_flags_set_to_one_with_credentials) that
patches os.environ with non-empty values for
GITHUB_CLIENT_ID/GITHUB_CLIENT_SECRET, GOOGLE_CLIENT_ID/GOOGLE_CLIENT_SECRET,
GITLAB_CLIENT_ID/GITLAB_CLIENT_SECRET and GITEA_CLIENT_ID/GITEA_CLIENT_SECRET,
calls call_command("configure_instance", stdout=...), and then iterates the same
keys
["IS_GOOGLE_ENABLED","IS_GITHUB_ENABLED","IS_GITLAB_ENABLED","IS_GITEA_ENABLED"]
asserting InstanceConfiguration.objects.get(key=key).value == "1" to ensure the
enabled-flag branch is exercised and catches any env-key mapping typos.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 18cc4612-dbbd-43c2-9786-aea8aa9b4321

📥 Commits

Reviewing files that changed from the base of the PR and between 7343ba0 and b1ebaad.

📒 Files selected for processing (3)
  • apps/api/plane/license/management/commands/configure_instance.py
  • apps/api/plane/tests/unit/management/__init__.py
  • apps/api/plane/tests/unit/management/test_configure_instance.py

…hing

The configure_instance management command checked whether *any* of the
four IS_*_ENABLED keys (Google, GitHub, GitLab, Gitea) existed before
deciding whether to create them.  Because IS_GITEA_ENABLED was already
seeded by instance_config_variables in the first loop, the exists()
check always returned True and the remaining three keys were silently
skipped.

This meant IS_GITHUB_ENABLED, IS_GOOGLE_ENABLED, and IS_GITLAB_ENABLED
were never created in the database.  Toggling these providers on in
God Mode sent a PATCH to update a non-existent row, which returned
200 OK but did nothing — the toggle appeared to succeed but reverted
on page reload.

Replace the all-or-nothing check with per-key get_or_create so each
flag is seeded independently of the others.  Move IS_GITEA_ENABLED out
of instance_config_variables (core.py) so that all four flags are
handled consistently in configure_instance with category AUTHENTICATION.

Signed-off-by: Mikhail Malyshev <mike.malyshev@gmail.com>
@rucoder rucoder force-pushed the fix/seed-oauth-enabled-flags-individually branch from b1ebaad to 9e504e7 Compare March 9, 2026 17:32
@rucoder rucoder requested a review from Copilot March 9, 2026 17:37
Copy link
Contributor
@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
apps/api/plane/tests/unit/management/test_configure_instance.py (1)

14-34: Good test coverage for the bug fix.

The tests comprehensively cover the key scenarios. One minor robustness improvement: consider using clear=True with patch.dict to ensure complete isolation from the CI/test runner environment:

with patch.dict("os.environ", {"SECRET_KEY": "test-secret"}, clear=True):

This prevents any OAuth env vars that might be set in the test environment from affecting the test behavior. Currently it works because these tests only check existence, not values, but clear=True would make the tests more deterministic.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/api/plane/tests/unit/management/test_configure_instance.py` around lines
14 - 34, Update the test
TestConfigureInstanceOAuthSeeding.test_creates_all_enabled_flags_when_none_exist
to use patch.dict with clear=True so the environment is fully isolated;
specifically, change the context manager call that currently uses
patch.dict("os.environ", {"SECRET_KEY": "test-secret"}) to include clear=True to
prevent stray OAuth env vars from affecting configure_instance behavior during
the call_command("configure_instance", stdout=out) invocation.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@apps/api/plane/tests/unit/management/test_configure_instance.py`:
- Around line 14-34: Update the test
TestConfigureInstanceOAuthSeeding.test_creates_all_enabled_flags_when_none_exist
to use patch.dict with clear=True so the environment is fully isolated;
specifically, change the context manager call that currently uses
patch.dict("os.environ", {"SECRET_KEY": "test-secret"}) to include clear=True to
prevent stray OAuth env vars from affecting configure_instance behavior during
the call_command("configure_instance", stdout=out) invocation.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 7bb8f54c-7dcb-430b-992f-47f47c6f90b4

📥 Commits

Reviewing files that changed from the base of the PR and between b1ebaad and 9e504e7.

📒 Files selected for processing (4)
  • apps/api/plane/license/management/commands/configure_instance.py
  • apps/api/plane/tests/unit/management/__init__.py
  • apps/api/plane/tests/unit/management/test_configure_instance.py
  • apps/api/plane/utils/instance_config_variables/core.py
💤 Files with no reviewable changes (1)
  • apps/api/plane/utils/instance_config_variables/core.py

Copy link
Contributor
Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 3 out of 4 changed files in this pull request and generated 1 comment.

config = InstanceConfiguration.objects.get(key=key)
assert config.value == "0", f"{key} should be '0' without credentials"

@pytest.mark.django_db
Copy link
Copilot AI Mar 9, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The test suite covers the negative path (no credentials → IS_*_ENABLED="0") but is missing a test for the positive path: when OAuth credentials are provided, the corresponding IS_*_ENABLED flag should be set to "1". Since SKIP_ENV_VAR defaults to True (DB mode), such a test would need to pre-create the credential rows (e.g., GITHUB_CLIENT_ID and GITHUB_CLIENT_SECRET) with non-empty values in the DB, then call configure_instance and assert that IS_GITHUB_ENABLED has value "1". Without this test, the credential-presence check logic in the lambda (line 86) is not exercised end-to-end.

Suggested change
@pytest.mark.django_db
@pytest.mark.django_db
def test_github_flag_set_to_one_with_db_credentials(self):
"""With GitHub OAuth credentials in DB, IS_GITHUB_ENABLED should be '1'."""
# Pre-create non-empty GitHub credential rows in the database (DB mode).
InstanceConfiguration.objects.create(
key="GITHUB_CLIENT_ID",
value="github-client-id",
category="AUTHENTICATION",
is_encrypted=False,
)
InstanceConfiguration.objects.create(
key="GITHUB_CLIENT_SECRET",
value="github-client-secret",
category="AUTHENTICATION",
is_encrypted=False,
)
out = StringIO()
# SKIP_ENV_VAR defaults to True, so the command should read credentials from DB.
with patch.dict("os.environ", {"SECRET_KEY": "test-secret"}):
call_command("configure_instance", stdout=out)
config = InstanceConfiguration.objects.get(key="IS_GITHUB_ENABLED")
assert config.value == "1", "IS_GITHUB_ENABLED should be '1' when DB credentials are present"
@pytest.mark.django_db

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

🐛 Bug: OAuth provider toggles (GitHub, Google, GitLab) silently fail because IS_*_ENABLED rows are never seeded

3 participants

0