feat: branch protection (#28526)

- Adds server endpoints for getting and setting protected branches
- Adds protected canvas view for branch protection
- Adds default branch and protected branch in git modal settings

Fixes #28434, #28056

Protected View -
<img width="1728" alt="image"
src="https://github.com/appsmithorg/appsmith/assets/8724051/4fb26450-61e1-4fc0-a66d-0ebaa28ff90c">

Branch Protection Settings -
<img width="1728" alt="image"
src="https://github.com/appsmithorg/appsmith/assets/8724051/fb6d16b6-0a3c-42fd-be1a-9b3677048663">

- New feature (non-breaking change which adds functionality)

>
> Please describe the tests that you ran to verify your changes. Also
list any relevant details for your test configuration.
> Delete anything that is not relevant
- [ ] Manual
- [ ] JUnit
- [ ] Jest
- [ ] Cypress
>
>
> Add Testsmith test cases links that relate to this PR
>
>
> Link issues raised during DP testing for better visiblity and tracking
(copy link from comments dropped on this PR)
>
>
>
- [ ] My code follows the style guidelines of this project
- [ ] I have performed a self-review of my own code
- [ ] I have commented my code, particularly in hard-to-understand areas
- [ ] I have made corresponding changes to the documentation
- [ ] My changes generate no new warnings
- [ ] I have added tests that prove my fix is effective or that my
feature works
- [ ] New and existing unit tests pass locally with my changes
- [ ] PR is being merged under a feature flag

- [ ] [Speedbreak
features](https://github.com/appsmithorg/TestSmith/wiki/Guidelines-for-test-plans#speedbreakers-)
have been covered
- [ ] Test plan covers all impacted features and [areas of
interest](https://github.com/appsmithorg/TestSmith/wiki/Guidelines-for-test-plans#areas-of-interest-)
- [ ] Test plan has been peer reviewed by project stakeholders and other
QA members
- [ ] Manually tested functionality on DP
- [ ] We had an implementation alignment call with stakeholders post QA
Round 2
- [ ] Cypress test cases have been added and approved by SDET/manual QA
- [ ] Added `Test Plan Approved` label after Cypress tests were reviewed
- [ ] Added `Test Plan Approved` label after JUnit tests were reviewed

---------

Co-authored-by: Nayan <nayan@appsmith.com>
This commit is contained in:
Rudraprasad Das 2023-11-03 22:43:36 +05:30 committed by Nayan
parent 5aa5318727
commit d24a0bf4b9
12 changed files with 125 additions and 240 deletions

View File

@ -474,6 +474,25 @@ export const fetchGitProtectedBranchesInit = () => {
};
};
export const fetchGitProtectedBranchesSuccess = (
protectedBranches: string[],
) => {
return {
type: ReduxActionTypes.GIT_FETCH_PROTECTED_BRANCHES_SUCCESS,
payload: { protectedBranches },
};
};
export const fetchGitProtectedBranchesError = (
error: any,
show: boolean = true,
) => {
return {
type: ReduxActionTypes.GIT_FETCH_PROTECTED_BRANCHES_ERROR,
payload: { error, show },
};
};
export const updateGitProtectedBranchesInit = (payload: {
protectedBranches: string[];
}) => {
@ -482,3 +501,19 @@ export const updateGitProtectedBranchesInit = (payload: {
payload,
};
};
export const updateGitProtectedBranchesSuccess = () => {
return {
type: ReduxActionTypes.GIT_UPDATE_PROTECTED_BRANCHES_SUCCESS,
};
};
export const updateGitProtectedBranchesError = (
error: any,
show: boolean = true,
) => {
return {
type: ReduxActionTypes.GIT_UPDATE_PROTECTED_BRANCHES_ERROR,
payload: { error, show },
};
};

View File

@ -508,7 +508,6 @@ export const PAGE_SERVER_UNAVAILABLE_ERROR_MESSAGES = (
export const POST = () => "Post";
export const CANCEL = () => "Cancel";
export const REMOVE = () => "Remove";
export const CREATE = () => "Create";
// Showcase Carousel
export const NEXT = () => "NEXT";
@ -1038,7 +1037,7 @@ export const PREVIOUS_STEP = () => "Previous step";
export const GIT_CONNECT_SUCCESS_TITLE = () =>
"Successfully connected to your Git remote repository";
export const GIT_CONNECT_SUCCESS_MESSAGE = () =>
"Now you can start collaborating with your team members by committing, merging and deploying your app";
"Right now, {branch} is set as the default branch and it is protected.";
export const START_USING_GIT = () => "Start using Git";
export const OPEN_GIT_SETTINGS = () => "Open Git settings";
export const GIT_AUTHOR = () => "Git author";
@ -1087,8 +1086,6 @@ export const GO_TO_SETTINGS = () => "Go to settings";
export const NOW_PROTECT_BRANCH = () =>
"You can now protect your default branch.";
export const APPSMITH_ENTERPRISE = () => "Appsmith Enterprise";
export const PROTECT_BRANCH_SUCCESS = () => "Changed protected branches";
export const UPDATE_DEFAULT_BRANCH_SUCCESS = () => "Updated default branch";
// Git Branch Protection end
export const NAV_DESCRIPTION = () =>

View File

@ -30,8 +30,6 @@ export const FEATURE_FLAG = {
release_app_sidebar_enabled: "release_app_sidebar_enabled",
release_git_branch_protection_enabled:
"release_git_branch_protection_enabled",
license_git_branch_protection_enabled:
"license_git_branch_protection_enabled",
license_widget_rtl_support_enabled: "license_widget_rtl_support_enabled",
} as const;
@ -62,7 +60,6 @@ export const DEFAULT_FEATURE_FLAG_VALUE: FeatureFlags = {
ab_show_templates_instead_of_blank_canvas_enabled: false,
release_app_sidebar_enabled: false,
release_git_branch_protection_enabled: false,
license_git_branch_protection_enabled: false,
license_widget_rtl_support_enabled: false,
};

View File

@ -115,6 +115,7 @@ export function EditorHeader() {
);
const isPreviewingApp =
isPreviewMode || isAppSettingsPaneWithNavigationTabOpen;
const isProtectedMode = useSelector(protectedModeSelector);
const [isPopoverOpen, setIsPopoverOpen] = useState<boolean>(false);
const [showModal, setShowModal] = useState(false);

View File

@ -136,7 +136,7 @@ function MainContainerWrapper(props: MainCanvasWrapperProps) {
const selectedTheme = useSelector(getSelectedAppTheme);
const params = useParams<{ applicationId: string; pageId: string }>();
const shouldHaveTopMargin =
!(isPreviewMode || isProtectedMode) ||
(!isPreviewMode && !isProtectedMode) ||
!isAppSettingsPaneWithNavigationTabOpen ||
pages.length > 1;
const isAppThemeChanging = useSelector(getAppThemeIsChanging);
@ -185,7 +185,7 @@ function MainContainerWrapper(props: MainCanvasWrapperProps) {
}
const isPreviewingNavigation =
isPreviewMode || isProtectedMode || isAppSettingsPaneWithNavigationTabOpen;
isPreviewMode || isAppSettingsPaneWithNavigationTabOpen;
/**
* calculating exact height to not allow scroll at this component,
@ -231,6 +231,7 @@ function MainContainerWrapper(props: MainCanvasWrapperProps) {
shouldHaveTopMargin &&
!showCanvasTopSection &&
!isPreviewingNavigation &&
!isProtectedMode &&
!showAnonymousDataPopup,
"mt-24": shouldShowSnapShotBanner,
})}

View File

@ -179,7 +179,7 @@ const getQuickActionButtons = ({
{
className: "t--bottom-bar-commit",
disabled: isProtectedMode,
count: isProtectedMode ? undefined : changesToCommit,
count: changesToCommit,
icon: "plus",
loading: isFetchingGitStatus,
onClick: commit,

View File

@ -8,7 +8,6 @@ import {
BRANCH_PROTECTION_RULE_1,
BRANCH_PROTECTION_RULE_2,
BRANCH_PROTECTION_RULE_3,
GIT_CONNECT_SUCCESS_MESSAGE,
GIT_CONNECT_SUCCESS_TITLE,
OPEN_GIT_SETTINGS,
START_USING_GIT,
@ -21,10 +20,7 @@ import { useDispatch, useSelector } from "react-redux";
import styled from "styled-components";
import { getCurrentAppGitMetaData } from "@appsmith/selectors/applicationSelectors";
import AnalyticsUtil from "utils/AnalyticsUtil";
import {
getDefaultGitBranchName,
getIsGitProtectedFeatureEnabled,
} from "selectors/gitSyncSelectors";
import { getDefaultGitBranchName } from "selectors/gitSyncSelectors";
const Container = styled.div``;
@ -57,18 +53,6 @@ const FeatureIcon = styled(Icon)`
margin-right: 4px;
`;
const BranchTag = styled(Tag)`
display: inline-flex;
`;
const DefaultBranchMessage = styled(Text)`
margin-bottom: 16px;
`;
const ProtectionRulesTitle = styled(Text)`
margin-bottom: 8px;
`;
const features = [
createMessage(BRANCH_PROTECTION_RULE_1),
createMessage(BRANCH_PROTECTION_RULE_2),
@ -78,9 +62,6 @@ const features = [
function ConnectionSuccess() {
const gitMetadata = useSelector(getCurrentAppGitMetaData);
const defaultBranchName = useSelector(getDefaultGitBranchName);
const isGitProtectedFeatureEnabled = useSelector(
getIsGitProtectedFeatureEnabled,
);
const dispatch = useDispatch();
useEffect(() => {
@ -110,55 +91,42 @@ function ConnectionSuccess() {
});
};
const preBranchProtectionContent = () => {
return (
<Text renderAs="p">{createMessage(GIT_CONNECT_SUCCESS_MESSAGE)}</Text>
);
};
const postBranchProtectionContent = () => {
return (
<>
<DefaultBranchMessage renderAs="p">
Right now,{" "}
<BranchTag isClosable={false}>{defaultBranchName}</BranchTag> is set
as the default branch and it is protected.
</DefaultBranchMessage>
<ProtectionRulesTitle renderAs="p">
{createMessage(BRANCH_PROTECTION_RULES_AS_FOLLOWS)}
</ProtectionRulesTitle>
<FeatureList>
{features.map((feature) => (
<FeatureItem key={feature}>
<FeatureIcon
color="var(--ads-v2-color-blue-600)"
name="oval-check"
size="md"
/>
<Text>{feature}</Text>
</FeatureItem>
))}
</FeatureList>
<Text>{createMessage(BRANCH_PROTECTION_CHANGE_RULE)}</Text>
</>
);
};
const preBranchProtectionActions = () => {
return (
<Button
data-testid="t--start-using-git-button"
onClick={handleStartGit}
size="md"
>
{createMessage(START_USING_GIT)}
</Button>
);
};
const postBranchProtectionActions = () => {
return (
<>
return (
<>
<ModalBody>
<Container>
<TitleContainer>
<StyledIcon color="#059669" name="oval-check" size="lg" />
<TitleText kind="heading-s" renderAs="h3">
{createMessage(GIT_CONNECT_SUCCESS_TITLE)}
</TitleText>
</TitleContainer>
<Text renderAs="p" style={{ marginBottom: 16 }}>
Right now,{" "}
<Tag isClosable={false} style={{ display: "inline-flex" }}>
{defaultBranchName}
</Tag>{" "}
is set as the default branch and it is protected.
</Text>
<Text renderAs="p" style={{ marginBottom: 8 }}>
{createMessage(BRANCH_PROTECTION_RULES_AS_FOLLOWS)}
</Text>
<FeatureList>
{features.map((feature) => (
<FeatureItem key={feature}>
<FeatureIcon
color="var(--ads-v2-color-blue-600)"
name="oval-check"
size="md"
/>
<Text>{feature}</Text>
</FeatureItem>
))}
</FeatureList>
<Text>{createMessage(BRANCH_PROTECTION_CHANGE_RULE)}</Text>
</Container>
</ModalBody>
<ModalFooter>
<Button
data-testid="t--start-using-git-button"
kind="secondary"
@ -170,29 +138,6 @@ function ConnectionSuccess() {
<Button onClick={handleOpenSettings} size="md">
{createMessage(OPEN_GIT_SETTINGS)}
</Button>
</>
);
};
return (
<>
<ModalBody>
<Container>
<TitleContainer>
<StyledIcon color="#059669" name="oval-check" size="lg" />
<TitleText kind="heading-s" renderAs="h3">
{createMessage(GIT_CONNECT_SUCCESS_TITLE)}
</TitleText>
</TitleContainer>
{isGitProtectedFeatureEnabled
? postBranchProtectionContent()
: preBranchProtectionContent()}
</Container>
</ModalBody>
<ModalFooter>
{isGitProtectedFeatureEnabled
? postBranchProtectionActions()
: preBranchProtectionActions()}
</ModalFooter>
</>
);

View File

@ -6,14 +6,12 @@ import {
createMessage,
} from "@appsmith/constants/messages";
import { updateGitDefaultBranch } from "actions/gitSyncActions";
import { isCEMode } from "@appsmith/utils";
import { Button, Link, Option, Select, Text } from "design-system";
import React, { useEffect, useMemo, useState } from "react";
import { useDispatch, useSelector } from "react-redux";
import { getGitBranches } from "selectors/gitSyncSelectors";
import styled from "styled-components";
import { useFeatureFlag } from "utils/hooks/useFeatureFlag";
import { FEATURE_FLAG } from "@appsmith/entities/FeatureFlag";
import { useAppsmithEnterpriseLink } from "./hooks";
const Container = styled.div`
padding-top: 16px;
@ -43,10 +41,9 @@ const StyledSelect = styled(Select)`
`;
function GitDefaultBranch() {
const isCE = isCEMode();
const dispatch = useDispatch();
const isGitProtectedFeatureLicensed = useFeatureFlag(
FEATURE_FLAG.license_git_branch_protection_enabled,
);
const unfilteredBranches = useSelector(getGitBranches);
const [selectedValue, setSelectedValue] = useState<string | undefined>();
@ -55,10 +52,6 @@ function GitDefaultBranch() {
return defaultBranch?.branchName;
}, [unfilteredBranches]);
const enterprisePricingLink = useAppsmithEnterpriseLink(
"git_branch_protection",
);
useEffect(() => {
const defaultBranch = unfilteredBranches.find((b) => b.default);
setSelectedValue(defaultBranch?.branchName);
@ -75,7 +68,7 @@ function GitDefaultBranch() {
};
const updateIsDisabled =
!selectedValue || selectedValue === currentDefaultBranch;
isCE && (!selectedValue || selectedValue === currentDefaultBranch);
return (
<Container>
@ -86,24 +79,21 @@ function GitDefaultBranch() {
<SectionDesc kind="body-m" renderAs="p">
{createMessage(DEFAULT_BRANCH_DESC)}
</SectionDesc>
{!isGitProtectedFeatureLicensed && (
<SectionDesc kind="body-m" renderAs="p">
To change your default branch, try{" "}
<Link
kind="primary"
style={{ display: "inline-flex" }}
target="_blank"
to={enterprisePricingLink}
>
{createMessage(APPSMITH_ENTERPRISE)}
</Link>
</SectionDesc>
)}
<SectionDesc kind="body-m" renderAs="p">
To change your default branch, try{" "}
<Link
kind="primary"
style={{ display: "inline-flex" }}
target="_blank"
to="https://www.appsmith.com/enterprise?lead_source=git%20feat%20branch%20config"
>
{createMessage(APPSMITH_ENTERPRISE)}
</Link>
</SectionDesc>
</HeadContainer>
<BodyContainer>
<StyledSelect
data-testid="t--git-default-branch-select"
isDisabled={!isGitProtectedFeatureLicensed}
isDisabled={isCE}
onChange={(v) => setSelectedValue(v)}
value={selectedValue}
>
@ -114,7 +104,6 @@ function GitDefaultBranch() {
))}
</StyledSelect>
<Button
data-testid="t--git-default-branch-update-btn"
isDisabled={updateIsDisabled}
kind="secondary"
onClick={handleUpdate}

View File

@ -5,6 +5,7 @@ import {
UPDATE,
createMessage,
} from "@appsmith/constants/messages";
import { isCEMode } from "@appsmith/utils";
import { updateGitProtectedBranchesInit } from "actions/gitSyncActions";
import { Button, Link, Option, Select, Text } from "design-system";
import { xor } from "lodash";
@ -17,9 +18,6 @@ import {
getProtectedBranchesSelector,
} from "selectors/gitSyncSelectors";
import styled from "styled-components";
import { useFeatureFlag } from "utils/hooks/useFeatureFlag";
import { FEATURE_FLAG } from "@appsmith/entities/FeatureFlag";
import { useAppsmithEnterpriseLink } from "./hooks";
const Container = styled.div`
padding-top: 16px;
@ -48,29 +46,19 @@ const StyledSelect = styled(Select)`
margin-right: 12px;
`;
const StyledLink = styled(Link)`
display: inline-flex;
`;
function GitProtectedBranches() {
const isCE = isCEMode();
const dispatch = useDispatch();
const unfilteredBranches = useSelector(getGitBranches);
const branches = unfilteredBranches.filter(
(b) => !b.branchName.includes("origin/"),
);
const isGitProtectedFeatureLicensed = useFeatureFlag(
FEATURE_FLAG.license_git_branch_protection_enabled,
);
const defaultBranch = useSelector(getDefaultGitBranchName);
const protectedBranches = useSelector(getProtectedBranchesSelector);
const isUpdateLoading = useSelector(getIsUpdateProtectedBranchesLoading);
const [selectedValues, setSelectedValues] = useState<string[]>();
const enterprisePricingLink = useAppsmithEnterpriseLink(
"git_branch_protection",
);
useEffect(() => {
setSelectedValues(protectedBranches);
}, []);
@ -98,22 +86,20 @@ function GitProtectedBranches() {
<SectionDesc kind="body-m" renderAs="p">
{createMessage(BRANCH_PROTECTION_DESC)}
</SectionDesc>
{!isGitProtectedFeatureLicensed && (
<SectionDesc kind="body-m" renderAs="p">
To protect multiple branches, try{" "}
<StyledLink
kind="primary"
target="_blank"
to={enterprisePricingLink}
>
{createMessage(APPSMITH_ENTERPRISE)}
</StyledLink>
</SectionDesc>
)}
<SectionDesc kind="body-m" renderAs="p">
To protect multiple branches, try{" "}
<Link
kind="primary"
style={{ display: "inline-flex" }}
target="_blank"
to="https://www.appsmith.com/enterprise?lead_source=git%20feat%20branch%20config"
>
{createMessage(APPSMITH_ENTERPRISE)}
</Link>
</SectionDesc>
</HeadContainer>
<BodyContainer>
<StyledSelect
data-testid="t--git-protected-branches-select"
isMultiSelect
maxTagTextLength={8}
onChange={(v) => setSelectedValues(v)}
@ -121,9 +107,7 @@ function GitProtectedBranches() {
>
{branches.map((b) => (
<Option
disabled={
!isGitProtectedFeatureLicensed && b.branchName !== defaultBranch
}
disabled={isCE && b.branchName !== defaultBranch}
key={b.branchName}
value={b.branchName}
>
@ -132,7 +116,6 @@ function GitProtectedBranches() {
))}
</StyledSelect>
<Button
data-testid="t--git-protected-branches-update-btn"
isDisabled={updateIsDisabled}
isLoading={isUpdateLoading}
kind="secondary"

View File

@ -34,9 +34,12 @@ import type {
import {
fetchGitRemoteStatusInit,
fetchGitProtectedBranchesInit,
updateGitProtectedBranchesInit,
fetchGitProtectedBranchesSuccess,
fetchGitProtectedBranchesError,
fetchGitRemoteStatusSuccess,
clearCommitSuccessfulState,
updateGitProtectedBranchesError,
updateGitProtectedBranchesSuccess,
updateGitProtectedBranchesInit,
} from "actions/gitSyncActions";
import {
commitToRepoSuccess,
@ -87,7 +90,6 @@ import {
ERROR_GIT_AUTH_FAIL,
ERROR_GIT_INVALID_REMOTE,
GIT_USER_UPDATED_SUCCESSFULLY,
PROTECT_BRANCH_SUCCESS,
} from "@appsmith/constants/messages";
import type { GitApplicationMetadata } from "@appsmith/api/ApplicationApi";
@ -253,7 +255,6 @@ function* connectToGitSaga(action: ConnectToGitReduxAction) {
/* commit effect START */
yield put(commitToRepoSuccess());
yield put(clearCommitSuccessfulState());
const curApplication: ApplicationPayload = yield select(
getCurrentApplication,
);
@ -1080,24 +1081,14 @@ function* fetchGitProtectedBranchesSaga() {
);
if (isValidResponse) {
const protectedBranches: string[] = response?.data;
yield put({
type: ReduxActionTypes.GIT_FETCH_PROTECTED_BRANCHES_SUCCESS,
payload: { protectedBranches },
});
yield put(fetchGitProtectedBranchesSuccess(protectedBranches));
} else {
yield put({
type: ReduxActionTypes.GIT_FETCH_PROTECTED_BRANCHES_ERROR,
payload: {
error: response?.responseMeta?.error?.message,
show: true,
},
});
yield put(
fetchGitProtectedBranchesError(response?.responseMeta?.error?.message),
);
}
} catch (error) {
yield put({
type: ReduxActionTypes.GIT_FETCH_PROTECTED_BRANCHES_ERROR,
payload: { error, show: true },
});
yield put(fetchGitProtectedBranchesError(error));
}
}
@ -1112,40 +1103,16 @@ function* updateGitProtectedBranchesSaga({
}
const { protectedBranches } = payload;
const applicationId: string = yield select(getCurrentApplicationId);
let response: ApiResponse<string[]>;
try {
response = yield call(
yield call(
GitSyncAPI.updateProtectedBranches,
applicationId,
protectedBranches,
);
const isValidResponse: boolean = yield validateResponse(
response,
false,
getLogToSentryFromResponse(response),
);
if (isValidResponse) {
yield put({
type: ReduxActionTypes.GIT_UPDATE_PROTECTED_BRANCHES_SUCCESS,
});
yield put(fetchGitProtectedBranchesInit());
toast.show(createMessage(PROTECT_BRANCH_SUCCESS), {
kind: "success",
});
} else {
yield put({
type: ReduxActionTypes.GIT_UPDATE_PROTECTED_BRANCHES_ERROR,
payload: {
error: response?.responseMeta?.error?.message,
show: true,
},
});
}
yield put(updateGitProtectedBranchesSuccess());
yield put(fetchGitProtectedBranchesInit());
} catch (error) {
yield put({
type: ReduxActionTypes.GIT_UPDATE_PROTECTED_BRANCHES_ERROR,
payload: { error, show: true },
});
yield put(updateGitProtectedBranchesError(error));
}
}

View File

@ -78,7 +78,6 @@ import org.eclipse.jgit.util.StringUtils;
import org.springframework.context.annotation.Import;
import org.springframework.dao.DuplicateKeyException;
import org.springframework.stereotype.Service;
import org.springframework.transaction.reactive.TransactionalOperator;
import reactor.core.Exceptions;
import reactor.core.observability.micrometer.Micrometer;
import reactor.core.publisher.Flux;
@ -151,7 +150,6 @@ public class GitServiceCEImpl implements GitServiceCE {
private final RedisUtils redisUtils;
private final ObservationRegistry observationRegistry;
private final GitPrivateRepoHelper gitPrivateRepoHelper;
private final TransactionalOperator transactionalOperator;
private static final Duration RETRY_DELAY = Duration.ofSeconds(1);
private static final Integer MAX_RETRIES = 20;
@ -3330,8 +3328,7 @@ public class GitServiceCEImpl implements GitServiceCE {
// user want to protect multiple branches, not allowed
return Mono.error(new AppsmithException(AppsmithError.UNSUPPORTED_OPERATION));
}
})
.as(transactionalOperator::transactional);
});
}
@Override

View File

@ -83,7 +83,6 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.boot.test.mock.mockito.SpyBean;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.core.io.buffer.DataBufferUtils;
@ -159,7 +158,7 @@ public class GitServiceCETest {
@Autowired
ApplicationPageService applicationPageService;
@SpyBean
@Autowired
ApplicationService applicationService;
@Autowired
@ -4354,7 +4353,7 @@ public class GitServiceCETest {
.assertNext(applicationList -> {
for (Application application : applicationList) {
GitApplicationMetadata metadata = application.getGitApplicationMetadata();
assertThat(metadata.getIsProtectedBranch()).isNotEqualTo(TRUE);
assertThat(metadata.getIsProtectedBranch()).isNotEqualTo(true);
if (application.getId().equals(defaultAppId)) {
// the default app should have the empty protected branch list
assertThat(metadata.getBranchProtectionRules()).isEmpty();
@ -4375,30 +4374,4 @@ public class GitServiceCETest {
.expectErrorMessage(AppsmithError.ACTION_IS_NOT_AUTHORIZED.getMessage("Protect branch"))
.verify();
}
@WithUserDetails("api_user")
@Test
public void updateProtectedBranches_WhenOneOperationFails_ChangesReverted() {
List<String> branchList = List.of("master", "develop", "feature");
// create three app with master as the default branch
String defaultAppId = createBranchedApplication(branchList);
Mockito.when(applicationService.updateProtectedBranches(Mockito.any(), Mockito.any()))
.thenReturn(Mono.error(new AppsmithException(AppsmithError.GENERIC_BAD_REQUEST, "Test error")));
Mono<List<String>> updateProtectedBranchesMono = gitService.updateProtectedBranches(defaultAppId, List.of());
StepVerifier.create(updateProtectedBranchesMono)
.expectErrorMessage(AppsmithError.GENERIC_BAD_REQUEST.getMessage("Test error"))
.verify();
StepVerifier.create(applicationService.findById(defaultAppId))
.assertNext(application -> {
GitApplicationMetadata metadata = application.getGitApplicationMetadata();
assertThat(metadata.getIsProtectedBranch()).isNotEqualTo(TRUE);
// the default app should have the empty protected branch list
assertThat(metadata.getBranchProtectionRules()).isNullOrEmpty();
})
.verifyComplete();
}
}