feat: secret variables in environments (#3779)
Co-authored-by: jamesgeorge007 <jamesgeorge998001@gmail.com>
This commit is contained in:
@@ -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)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -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
|
||||
})
|
||||
}
|
||||
|
||||
@@ -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",
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
@@ -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",
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user