feat: secret variables in environments (#3779)

Co-authored-by: jamesgeorge007 <jamesgeorge998001@gmail.com>
This commit is contained in:
Nivedin
2024-02-08 21:58:42 +05:30
committed by GitHub
parent 16803acb26
commit 00862eb192
55 changed files with 2141 additions and 439 deletions

View File

@@ -15,9 +15,21 @@ vi.mock("~/newstore/environments", async () => {
return {
__esModule: true,
aggregateEnvs$: new BehaviorSubject([
{ key: "EXISTING_ENV_VAR", value: "test_value" },
aggregateEnvsWithSecrets$: new BehaviorSubject([
{ key: "EXISTING_ENV_VAR", value: "test_value", secret: false },
{ key: "EXISTING_ENV_VAR_2", value: "", secret: false },
]),
getCurrentEnvironment: () => ({
id: "1",
name: "some-env",
v: 1,
variables: {
key: "EXISTING_ENV_VAR",
value: "test_value",
secret: false,
},
}),
getSelectedEnvironmentType: () => "MY_ENV",
}
})
@@ -51,7 +63,7 @@ describe("EnvironmentInspectorService", () => {
expect(result.value).toContainEqual(
expect.objectContaining({
id: "environment",
id: "environment-not-found-0",
isApplicable: true,
text: {
type: "text",
@@ -91,7 +103,7 @@ describe("EnvironmentInspectorService", () => {
expect(result.value).toContainEqual(
expect.objectContaining({
id: "environment",
id: "environment-not-found-0",
isApplicable: true,
text: {
type: "text",
@@ -134,7 +146,7 @@ describe("EnvironmentInspectorService", () => {
expect(result.value).toContainEqual(
expect.objectContaining({
id: "environment",
id: "environment-not-found-0",
isApplicable: true,
text: {
type: "text",
@@ -161,5 +173,103 @@ describe("EnvironmentInspectorService", () => {
expect(result.value).toHaveLength(0)
})
it("should return an inspector result when the URL contains empty value in a environment variable", () => {
const container = new TestContainer()
const envInspector = container.bind(EnvironmentInspectorService)
const req = ref({
...getDefaultRESTRequest(),
endpoint: "<<EXISTING_ENV_VAR_2>>",
})
const result = envInspector.getInspections(req)
expect(result.value).toHaveLength(1)
})
it("should not return an inspector result when the URL contains non empty value in a environemnt variable", () => {
const container = new TestContainer()
const envInspector = container.bind(EnvironmentInspectorService)
const req = ref({
...getDefaultRESTRequest(),
endpoint: "<<EXISTING_ENV_VAR>>",
})
const result = envInspector.getInspections(req)
expect(result.value).toHaveLength(0)
})
it("should return an inspector result when the headers contain empty value in a environment variable", () => {
const container = new TestContainer()
const envInspector = container.bind(EnvironmentInspectorService)
const req = ref({
...getDefaultRESTRequest(),
endpoint: "http://example.com/api/data",
headers: [
{ key: "<<EXISTING_ENV_VAR_2>>", value: "some-value", active: true },
],
})
const result = envInspector.getInspections(req)
expect(result.value).toHaveLength(1)
})
it("should not return an inspector result when the headers contain non empty value in a environemnt variable", () => {
const container = new TestContainer()
const envInspector = container.bind(EnvironmentInspectorService)
const req = ref({
...getDefaultRESTRequest(),
endpoint: "http://example.com/api/data",
headers: [
{ key: "<<EXISTING_ENV_VAR>>", value: "some-value", active: true },
],
})
const result = envInspector.getInspections(req)
expect(result.value).toHaveLength(0)
})
it("should return an inspector result when the params contain empty value in a environment variable", () => {
const container = new TestContainer()
const envInspector = container.bind(EnvironmentInspectorService)
const req = ref({
...getDefaultRESTRequest(),
endpoint: "http://example.com/api/data",
headers: [],
params: [
{ key: "<<EXISTING_ENV_VAR_2>>", value: "some-value", active: true },
],
})
const result = envInspector.getInspections(req)
expect(result.value).toHaveLength(1)
})
it("should not return an inspector result when the params contain non empty value in a environemnt variable", () => {
const container = new TestContainer()
const envInspector = container.bind(EnvironmentInspectorService)
const req = ref({
...getDefaultRESTRequest(),
endpoint: "http://example.com/api/data",
headers: [],
params: [
{ key: "<<EXISTING_ENV_VAR>>", value: "some-value", active: true },
],
})
const result = envInspector.getInspections(req)
expect(result.value).toHaveLength(0)
})
})
})

View File

@@ -9,7 +9,11 @@ import { Service } from "dioc"
import { Ref, markRaw } from "vue"
import IconPlusCircle from "~icons/lucide/plus-circle"
import { HoppRESTRequest } from "@hoppscotch/data"
import { aggregateEnvs$ } from "~/newstore/environments"
import {
aggregateEnvsWithSecrets$,
getCurrentEnvironment,
getSelectedEnvironmentType,
} from "~/newstore/environments"
import { invokeAction } from "~/helpers/actions"
import { computed } from "vue"
import { useStreamStatic } from "~/composables/stream"
@@ -36,9 +40,13 @@ export class EnvironmentInspectorService extends Service implements Inspector {
private readonly inspection = this.bind(InspectionService)
private aggregateEnvs = useStreamStatic(aggregateEnvs$, [], () => {
/* noop */
})[0]
private aggregateEnvsWithSecrets = useStreamStatic(
aggregateEnvsWithSecrets$,
[],
() => {
/* noop */
}
)[0]
constructor() {
super()
@@ -49,9 +57,8 @@ export class EnvironmentInspectorService extends Service implements Inspector {
/**
* Validates the environment variables in the target array
* @param target The target array to validate
* @param results The results array to push the results to
* @param locations The location where results are to be displayed
* @returns The results array
* @returns The results array containing the results of the validation
*/
private validateEnvironmentVariables = (
target: any[],
@@ -59,7 +66,7 @@ export class EnvironmentInspectorService extends Service implements Inspector {
) => {
const newErrors: InspectorResult[] = []
const envKeys = this.aggregateEnvs.value.map((e) => e.key)
const envKeys = this.aggregateEnvsWithSecrets.value.map((e) => e.key)
target.forEach((element, index) => {
if (isENVInString(element)) {
@@ -68,29 +75,20 @@ export class EnvironmentInspectorService extends Service implements Inspector {
if (extractedEnv) {
extractedEnv.forEach((exEnv: string) => {
const formattedExEnv = exEnv.slice(2, -2)
let itemLocation: InspectorLocation
if (locations.type === "header") {
itemLocation = {
type: "header",
position: locations.position,
index: index,
key: element,
}
} else if (locations.type === "parameter") {
itemLocation = {
type: "parameter",
position: locations.position,
index: index,
key: element,
}
} else {
itemLocation = {
type: "url",
}
const itemLocation: InspectorLocation = {
type: locations.type,
position:
locations.type === "url" ||
locations.type === "body" ||
locations.type === "response"
? "key"
: locations.position,
index: index,
key: element,
}
if (!envKeys.includes(formattedExEnv)) {
newErrors.push({
id: "environment",
id: `environment-not-found-${newErrors.length}`,
text: {
type: "text",
text: this.t("inspections.environment.not_found", {
@@ -112,7 +110,7 @@ export class EnvironmentInspectorService extends Service implements Inspector {
locations: itemLocation,
doc: {
text: this.t("action.learn_more"),
link: "https://docs.hoppscotch.io/",
link: "https://docs.hoppscotch.io/documentation/features/inspections",
},
})
}
@@ -124,6 +122,103 @@ export class EnvironmentInspectorService extends Service implements Inspector {
return newErrors
}
/**
* Checks if the environment variables in the target array are empty
* @param target The target array to validate
* @param locations The location where results are to be displayed
* @returns The results array containing the results of the validation
*/
private validateEmptyEnvironmentVariables = (
target: any[],
locations: InspectorLocation
) => {
const newErrors: InspectorResult[] = []
target.forEach((element, index) => {
if (isENVInString(element)) {
const extractedEnv = element.match(HOPP_ENVIRONMENT_REGEX)
if (extractedEnv) {
extractedEnv.forEach((exEnv: string) => {
const formattedExEnv = exEnv.slice(2, -2)
this.aggregateEnvsWithSecrets.value.forEach((env) => {
if (env.key === formattedExEnv) {
if (env.value === "") {
const itemLocation: InspectorLocation = {
type: locations.type,
position:
locations.type === "url" ||
locations.type === "body" ||
locations.type === "response"
? "key"
: locations.position,
index: index,
key: element,
}
const currentSelectedEnvironment = getCurrentEnvironment()
const currentEnvironmentType = getSelectedEnvironmentType()
let invokeActionType:
| "modals.my.environment.edit"
| "modals.team.environment.edit"
| "modals.global.environment.update" =
"modals.my.environment.edit"
if (env.sourceEnv === "Global") {
invokeActionType = "modals.global.environment.update"
} else if (currentEnvironmentType === "MY_ENV") {
invokeActionType = "modals.my.environment.edit"
} else if (currentEnvironmentType === "TEAM_ENV") {
invokeActionType = "modals.team.environment.edit"
} else {
invokeActionType = "modals.my.environment.edit"
}
newErrors.push({
id: `environment-empty-${newErrors.length}`,
text: {
type: "text",
text: this.t("inspections.environment.empty_value", {
variable: exEnv,
}),
},
icon: markRaw(IconPlusCircle),
action: {
text: this.t(
"inspections.environment.add_environment_value"
),
apply: () => {
invokeAction(invokeActionType, {
envName:
env.sourceEnv !== "Global"
? currentSelectedEnvironment.name
: "Global",
variableName: formattedExEnv,
isSecret: env.secret,
})
},
},
severity: 2,
isApplicable: true,
locations: itemLocation,
doc: {
text: this.t("action.learn_more"),
link: "https://docs.hoppscotch.io/documentation/features/inspections",
},
})
}
}
})
})
}
}
})
return newErrors
}
getInspections(req: Readonly<Ref<HoppRESTRequest>>) {
return computed(() => {
const results: InspectorResult[] = []
@@ -132,12 +227,25 @@ export class EnvironmentInspectorService extends Service implements Inspector {
const params = req.value.params
/**
* Validate the environment variables in the URL
*/
const url = req.value.endpoint
results.push(
...this.validateEnvironmentVariables([req.value.endpoint], {
...this.validateEnvironmentVariables([url], {
type: "url",
})
)
results.push(
...this.validateEmptyEnvironmentVariables([url], {
type: "url",
})
)
/**
* Validate the environment variables in the headers
*/
const headerKeys = Object.values(headers).map((header) => header.key)
results.push(
@@ -146,6 +254,12 @@ export class EnvironmentInspectorService extends Service implements Inspector {
position: "key",
})
)
results.push(
...this.validateEmptyEnvironmentVariables(headerKeys, {
type: "header",
position: "key",
})
)
const headerValues = Object.values(headers).map((header) => header.value)
@@ -155,7 +269,16 @@ export class EnvironmentInspectorService extends Service implements Inspector {
position: "value",
})
)
results.push(
...this.validateEmptyEnvironmentVariables(headerValues, {
type: "header",
position: "value",
})
)
/**
* Validate the environment variables in the parameters
*/
const paramsKeys = Object.values(params).map((param) => param.key)
results.push(
@@ -164,6 +287,12 @@ export class EnvironmentInspectorService extends Service implements Inspector {
position: "key",
})
)
results.push(
...this.validateEmptyEnvironmentVariables(paramsKeys, {
type: "parameter",
position: "key",
})
)
const paramsValues = Object.values(params).map((param) => param.value)
@@ -174,6 +303,13 @@ export class EnvironmentInspectorService extends Service implements Inspector {
})
)
results.push(
...this.validateEmptyEnvironmentVariables(paramsValues, {
type: "parameter",
position: "value",
})
)
return results
})
}

View File

@@ -67,7 +67,7 @@ export class HeaderInspectorService extends Service implements Inspector {
},
doc: {
text: this.t("action.learn_more"),
link: "https://docs.hoppscotch.io/",
link: "https://docs.hoppscotch.io/documentation/features/inspections",
},
})
}

View File

@@ -67,7 +67,7 @@ export class ResponseInspectorService extends Service implements Inspector {
},
doc: {
text: this.t("action.learn_more"),
link: "https://docs.hoppscotch.io/",
link: "https://docs.hoppscotch.io/documentation/features/inspections",
},
})
}