mirror of
https://github.com/usebruno/bruno.git
synced 2026-06-11 09:51:30 +00:00
feat: Import Insomnia environments (#5716)
* feat: Implement environment conversion utilities for Insomnia to Bruno migration fix tests fix: test feat: updated `toBrunoEnv` and merging functions to flatten environment data using dot-notation keys. added tests for `buildV5Environments` and `buildV4Environments` to verify flattened key behavior and shallow overrides. chore: update package-lock.json refactor: replace `flat` library with custom `flattenObject` utility for improved environment data flattening chore: remove package-lock.json updates feat: update `toBrunoEnv` to convert environment values to strings and adjust tests for flattened key behavior in Insomnia environment imports refactor: update flattening logic to use JavaScript-style square bracket notation for arrays and adjust related tests feat: enhance insomnia-to-bruno conversion by normalizing variables in requests, and add tests for v4 and v5 environment imports refactor: improve variable naming and streamline environment building logic in `buildV5Environments` and `buildV4Environments` functions test: add cleanup step to environment import tests and update expected version for new feature * revert package-lock.json changes * test: Add data-testid attributes to environment variable rows in EnvironmentVariables component
This commit is contained in:
@@ -185,7 +185,7 @@ const EnvironmentVariables = ({ environment, collection, setIsModified, original
|
||||
</thead>
|
||||
<tbody>
|
||||
{formik.values.map((variable, index) => (
|
||||
<tr key={variable.uid}>
|
||||
<tr key={variable.uid} data-testid={`env-var-row-${variable.name}`}>
|
||||
<td className="text-center">
|
||||
<input
|
||||
type="checkbox"
|
||||
|
||||
@@ -125,7 +125,7 @@ const EnvironmentVariables = ({ environment, setIsModified, originalEnvironmentV
|
||||
</thead>
|
||||
<tbody>
|
||||
{formik.values.map((variable, index) => (
|
||||
<tr key={variable.uid}>
|
||||
<tr key={variable.uid} data-testid={`env-var-row-${variable.name}`}>
|
||||
<td className="text-center">
|
||||
<input
|
||||
type="checkbox"
|
||||
|
||||
90
packages/bruno-converters/src/insomnia/env-utils.js
Normal file
90
packages/bruno-converters/src/insomnia/env-utils.js
Normal file
@@ -0,0 +1,90 @@
|
||||
import { uuid } from '../common';
|
||||
import { flattenObject } from '../utils/flatten';
|
||||
|
||||
/**
|
||||
* Converts an Insomnia environment node into a Bruno environment using JSON-path-like keys.
|
||||
* - Flattens env.data to dot-notation keys; values are converted to strings.
|
||||
*/
|
||||
export const toBrunoEnv = (env, index = 0) => {
|
||||
const variables = [];
|
||||
const flatEnvData = flattenObject(env?.data || {});
|
||||
Object.entries(flatEnvData).forEach(([name, value]) => {
|
||||
variables.push({
|
||||
uid: uuid(),
|
||||
name,
|
||||
value: String(value),
|
||||
type: 'text',
|
||||
enabled: true,
|
||||
secret: false
|
||||
});
|
||||
});
|
||||
|
||||
return {
|
||||
uid: uuid(),
|
||||
name: (env?.name && String(env.name).trim()) || `Environment ${index + 1}`,
|
||||
variables
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Shallowly merges two flattened env data objects.
|
||||
* - Keys in override replace keys in base.
|
||||
* - No recursive merging.
|
||||
*/
|
||||
const shallowMergeFlat = (baseFlat = {}, overrideFlat = {}) => ({ ...baseFlat, ...overrideFlat });
|
||||
|
||||
/**
|
||||
* Builds Bruno environments from Insomnia v5 environments.
|
||||
* - Expects a single object (base env) with optional subEnvironments.
|
||||
* - Creates one env for base and one env per sub using flattened, shallow-merged keys.
|
||||
*/
|
||||
export const buildV5Environments = (baseEnv) => {
|
||||
if (!baseEnv || typeof baseEnv !== 'object') return [];
|
||||
|
||||
const result = [];
|
||||
|
||||
// include base as standalone
|
||||
result.push(toBrunoEnv(baseEnv));
|
||||
|
||||
const subs = Array.isArray(baseEnv.subEnvironments) ? baseEnv.subEnvironments : [];
|
||||
const baseFlat = flattenObject(baseEnv?.data || {});
|
||||
subs.forEach((sub, i) => {
|
||||
const subFlat = flattenObject(sub?.data || {});
|
||||
const mergedFlat = shallowMergeFlat(baseFlat, subFlat);
|
||||
result.push(toBrunoEnv({ name: sub?.name, data: mergedFlat }, i + 1));
|
||||
});
|
||||
return result;
|
||||
};
|
||||
|
||||
/**
|
||||
* Builds Bruno environments from Insomnia v4 resources.
|
||||
* - Base env: parentId equals workspaceId; included as-is (flattened).
|
||||
* - Sub envs: merge base (flattened) with sub (flattened) and import.
|
||||
*
|
||||
* Note: Insomnia supports only ONE base environment per workspace.
|
||||
*/
|
||||
export const buildV4Environments = (resources, workspaceId) => {
|
||||
const allEnvResources = resources.filter((r) => r._type === 'environment') || [];
|
||||
const envById = {};
|
||||
allEnvResources.forEach((e) => (envById[e._id] = e));
|
||||
|
||||
const isBaseEnv = (env) => env.parentId === workspaceId;
|
||||
|
||||
const result = [];
|
||||
|
||||
const baseEnv = allEnvResources.find(isBaseEnv);
|
||||
if (baseEnv) {
|
||||
result.push(toBrunoEnv(baseEnv));
|
||||
}
|
||||
|
||||
// sub envs - all inherit from the single base environment
|
||||
const subEnvs = allEnvResources.filter((e) => !isBaseEnv(e));
|
||||
const baseFlat = flattenObject(baseEnv?.data || {});
|
||||
subEnvs.forEach((sub, idx) => {
|
||||
const subFlat = flattenObject(sub.data || {});
|
||||
const mergedFlat = shallowMergeFlat(baseFlat, subFlat);
|
||||
result.push(toBrunoEnv({ name: sub.name, data: mergedFlat }, idx + 1));
|
||||
});
|
||||
|
||||
return result;
|
||||
};
|
||||
@@ -2,13 +2,14 @@ import each from 'lodash/each';
|
||||
import get from 'lodash/get';
|
||||
import jsyaml from 'js-yaml';
|
||||
import { validateSchema, transformItemsInCollection, hydrateSeqInCollection, uuid } from '../common';
|
||||
import { buildV5Environments, buildV4Environments } from './env-utils';
|
||||
|
||||
const parseGraphQL = (text) => {
|
||||
try {
|
||||
const graphql = JSON.parse(text);
|
||||
|
||||
return {
|
||||
query: graphql.query,
|
||||
query: normalizeVariables(graphql.query),
|
||||
variables: JSON.stringify(graphql.variables, null, 2)
|
||||
};
|
||||
} catch (e) {
|
||||
@@ -49,7 +50,7 @@ const transformInsomniaRequestItem = (request, index, allRequests) => {
|
||||
name,
|
||||
type: 'http-request',
|
||||
request: {
|
||||
url: request.url,
|
||||
url: normalizeVariables(request.url),
|
||||
method: request.method,
|
||||
auth: {
|
||||
mode: 'none',
|
||||
@@ -74,7 +75,7 @@ const transformInsomniaRequestItem = (request, index, allRequests) => {
|
||||
brunoRequestItem.request.headers.push({
|
||||
uid: uuid(),
|
||||
name: header.name,
|
||||
value: header.value,
|
||||
value: normalizeVariables(header.value),
|
||||
description: header.description,
|
||||
enabled: !header.disabled
|
||||
});
|
||||
@@ -84,7 +85,7 @@ const transformInsomniaRequestItem = (request, index, allRequests) => {
|
||||
brunoRequestItem.request.params.push({
|
||||
uid: uuid(),
|
||||
name: param.name,
|
||||
value: param.value,
|
||||
value: normalizeVariables(param.value),
|
||||
description: param.description,
|
||||
type: 'query',
|
||||
enabled: !param.disabled
|
||||
@@ -95,7 +96,7 @@ const transformInsomniaRequestItem = (request, index, allRequests) => {
|
||||
brunoRequestItem.request.params.push({
|
||||
uid: uuid(),
|
||||
name: param.name,
|
||||
value: param.value,
|
||||
value: normalizeVariables(param.value),
|
||||
description: '',
|
||||
type: 'path',
|
||||
enabled: true
|
||||
@@ -121,14 +122,14 @@ const transformInsomniaRequestItem = (request, index, allRequests) => {
|
||||
|
||||
if (mimeType === 'application/json') {
|
||||
brunoRequestItem.request.body.mode = 'json';
|
||||
brunoRequestItem.request.body.json = request.body.text;
|
||||
brunoRequestItem.request.body.json = normalizeVariables(request.body.text);
|
||||
} else if (mimeType === 'application/x-www-form-urlencoded') {
|
||||
brunoRequestItem.request.body.mode = 'formUrlEncoded';
|
||||
each(request.body.params, (param) => {
|
||||
brunoRequestItem.request.body.formUrlEncoded.push({
|
||||
uid: uuid(),
|
||||
name: param.name,
|
||||
value: param.value,
|
||||
value: normalizeVariables(param.value),
|
||||
description: param.description,
|
||||
enabled: !param.disabled
|
||||
});
|
||||
@@ -140,17 +141,17 @@ const transformInsomniaRequestItem = (request, index, allRequests) => {
|
||||
uid: uuid(),
|
||||
type: 'text',
|
||||
name: param.name,
|
||||
value: param.value,
|
||||
value: normalizeVariables(param.value),
|
||||
description: param.description,
|
||||
enabled: !param.disabled
|
||||
});
|
||||
});
|
||||
} else if (mimeType === 'text/plain') {
|
||||
brunoRequestItem.request.body.mode = 'text';
|
||||
brunoRequestItem.request.body.text = request.body.text;
|
||||
brunoRequestItem.request.body.text = normalizeVariables(request.body.text);
|
||||
} else if (mimeType === 'text/xml' || mimeType === 'application/xml') {
|
||||
brunoRequestItem.request.body.mode = 'xml';
|
||||
brunoRequestItem.request.body.xml = request.body.text;
|
||||
brunoRequestItem.request.body.xml = normalizeVariables(request.body.text);
|
||||
} else if (mimeType === 'application/graphql') {
|
||||
brunoRequestItem.type = 'graphql-request';
|
||||
brunoRequestItem.request.body.mode = 'graphql';
|
||||
@@ -229,7 +230,7 @@ const parseInsomniaV5Collection = (data) => {
|
||||
|
||||
// Parse environments if available
|
||||
if (data.environments) {
|
||||
// Handle environments implementation if needed
|
||||
brunoCollection.environments = buildV5Environments(data.environments);
|
||||
}
|
||||
|
||||
return brunoCollection;
|
||||
@@ -287,6 +288,9 @@ const parseInsomniaCollection = (data) => {
|
||||
}
|
||||
|
||||
brunoCollection.items = createFolderStructure(requestsAndFolders, insomniaCollection._id);
|
||||
|
||||
// Build environments from resources
|
||||
brunoCollection.environments = buildV4Environments(insomniaResources, insomniaCollection._id);
|
||||
return brunoCollection;
|
||||
} catch (err) {
|
||||
console.error('Error parsing collection:', err);
|
||||
|
||||
51
packages/bruno-converters/src/utils/flatten.js
Normal file
51
packages/bruno-converters/src/utils/flatten.js
Normal file
@@ -0,0 +1,51 @@
|
||||
// Adapted from flat library by Hugh Kennedy (https://github.com/hughsk/flat)
|
||||
// MIT License
|
||||
|
||||
/**
|
||||
* Recursively flattens a nested object or array into a flat object with JavaScript-style keys.
|
||||
* Arrays use square bracket notation (e.g., items[0].id).
|
||||
* Only primitives and null are included as values.
|
||||
*
|
||||
* @param {object|array} obj - The object or array to flatten.
|
||||
* @param {string} [prefix] - Used internally for recursion to build the path.
|
||||
* @returns {object} A flat object with JavaScript-style keys.
|
||||
*/
|
||||
function flattenObject(obj, prefix = '') {
|
||||
// Store the final flat result
|
||||
const result = {};
|
||||
|
||||
/**
|
||||
* Internal recursive function to process each value.
|
||||
* @param {*} value - The current value (can be object, array, primitive, or null)
|
||||
* @param {string} path - The JavaScript-style key up to this point
|
||||
*/
|
||||
function step(value, path) {
|
||||
// If value is a primitive (string, number, boolean) or null, add it to the result
|
||||
if (value === null || typeof value !== 'object') {
|
||||
result[path] = value;
|
||||
return;
|
||||
}
|
||||
|
||||
// If value is an array, iterate over each item by index
|
||||
if (Array.isArray(value)) {
|
||||
value.forEach((item, idx) => {
|
||||
// Build the next path with array index using square brackets (e.g. "items[0]")
|
||||
step(item, path ? `${path}[${idx}]` : `[${idx}]`);
|
||||
});
|
||||
} else {
|
||||
// If value is an object, iterate over its keys
|
||||
Object.entries(value).forEach(([key, val]) => {
|
||||
// Build the next path with object key (e.g. "user.name")
|
||||
step(val, path ? `${path}.${key}` : key);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Start recursive flattening from the root object
|
||||
step(obj, prefix);
|
||||
|
||||
// Return the flat result object
|
||||
return result;
|
||||
}
|
||||
|
||||
export { flattenObject };
|
||||
100
packages/bruno-converters/tests/insomnia/env-utils.spec.js
Normal file
100
packages/bruno-converters/tests/insomnia/env-utils.spec.js
Normal file
@@ -0,0 +1,100 @@
|
||||
import { describe, it, expect } from '@jest/globals';
|
||||
import { buildV5Environments, buildV4Environments } from '../../src/insomnia/env-utils';
|
||||
|
||||
const getVar = (env, name) => {
|
||||
return env.variables.find((v) => v.name === name);
|
||||
};
|
||||
|
||||
describe('env-utils', () => {
|
||||
describe('buildV5Environments', () => {
|
||||
it('creates base and sub environments with flattened keys and shallow overrides', () => {
|
||||
const environmentsNode = {
|
||||
name: 'Base',
|
||||
data: {
|
||||
baseurl: 'https://api.example.com',
|
||||
nested: { name: 'alice', roles: ['admin'] },
|
||||
numbers: [1, 2]
|
||||
},
|
||||
subEnvironments: [
|
||||
{
|
||||
name: 'Staging',
|
||||
data: {
|
||||
baseurl: 'https://staging.example.com',
|
||||
nested: { name: 'bob' }
|
||||
}
|
||||
},
|
||||
{ name: 'Dev', data: {} }
|
||||
]
|
||||
};
|
||||
|
||||
const envs = buildV5Environments(environmentsNode);
|
||||
expect(envs.length).toBe(3);
|
||||
|
||||
const base = envs[0];
|
||||
const staging = envs[1];
|
||||
const dev = envs[2];
|
||||
|
||||
expect(base.name).toBe('Base');
|
||||
expect(getVar(base, 'baseurl')?.value).toBe('https://api.example.com');
|
||||
expect(getVar(base, 'nested.name')?.value).toBe('alice');
|
||||
expect(getVar(base, 'nested.roles[0]')?.value).toBe('admin');
|
||||
expect(getVar(base, 'numbers[1]')?.value).toBe('2');
|
||||
|
||||
expect(staging.name).toBe('Staging');
|
||||
// baseurl overridden in sub
|
||||
expect(getVar(staging, 'baseurl')?.value).toBe('https://staging.example.com');
|
||||
// nested.name overridden, nested array preserved from base
|
||||
expect(getVar(staging, 'nested.name')?.value).toBe('bob');
|
||||
expect(getVar(staging, 'nested.roles[0]')?.value).toBe('admin');
|
||||
|
||||
expect(dev.name).toBe('Dev');
|
||||
// no sub data => inherits base
|
||||
expect(getVar(dev, 'baseurl')?.value).toBe('https://api.example.com');
|
||||
expect(getVar(dev, 'nested.name')?.value).toBe('alice');
|
||||
});
|
||||
});
|
||||
|
||||
describe('buildV4Environments', () => {
|
||||
it('merges nearest base and sub env data (flattened) into standalone Bruno envs', () => {
|
||||
const workspaceId = 'wrk_1';
|
||||
const resources = [
|
||||
{ _id: workspaceId, _type: 'workspace', name: 'WS' },
|
||||
{
|
||||
_id: 'env_base',
|
||||
_type: 'environment',
|
||||
parentId: workspaceId,
|
||||
name: 'Base',
|
||||
data: {
|
||||
baseurl: 'https://api.example.com',
|
||||
user: { name: 'alice' },
|
||||
arr: [{ id: 1 }]
|
||||
}
|
||||
},
|
||||
{
|
||||
_id: 'env_sub',
|
||||
_type: 'environment',
|
||||
parentId: 'env_base',
|
||||
name: 'Sub',
|
||||
data: {
|
||||
user: { name: 'bob' }
|
||||
}
|
||||
}
|
||||
];
|
||||
|
||||
const envs = buildV4Environments(resources, workspaceId);
|
||||
expect(envs.length).toBe(2);
|
||||
|
||||
const base = envs.find((e) => e.name === 'Base');
|
||||
const sub = envs.find((e) => e.name === 'Sub');
|
||||
|
||||
expect(getVar(base, 'baseurl')?.value).toBe('https://api.example.com');
|
||||
expect(getVar(base, 'user.name')?.value).toBe('alice');
|
||||
expect(getVar(base, 'arr[0].id')?.value).toBe('1');
|
||||
|
||||
// sub should inherit base, override user.name
|
||||
expect(getVar(sub, 'baseurl')?.value).toBe('https://api.example.com');
|
||||
expect(getVar(sub, 'user.name')?.value).toBe('bob');
|
||||
expect(getVar(sub, 'arr[0].id')?.value).toBe('1');
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -84,7 +84,27 @@ environments:
|
||||
`
|
||||
|
||||
const expectedOutput = {
|
||||
"environments": [],
|
||||
environments: [
|
||||
{
|
||||
name: 'Imported Environment',
|
||||
variables: [
|
||||
{
|
||||
name: 'var1',
|
||||
value: 'value1',
|
||||
type: 'text',
|
||||
enabled: true,
|
||||
secret: false
|
||||
},
|
||||
{
|
||||
name: 'var2',
|
||||
value: 'value2',
|
||||
type: 'text',
|
||||
enabled: true,
|
||||
secret: false
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"items": [
|
||||
{
|
||||
"items": [
|
||||
|
||||
@@ -65,7 +65,27 @@ const insomniaCollection = {
|
||||
};
|
||||
|
||||
const expectedOutput = {
|
||||
"environments": [],
|
||||
environments: [
|
||||
{
|
||||
name: 'Environment 1',
|
||||
variables: [
|
||||
{
|
||||
name: 'var1',
|
||||
value: 'value1',
|
||||
type: 'text',
|
||||
enabled: true,
|
||||
secret: false
|
||||
},
|
||||
{
|
||||
name: 'var2',
|
||||
value: 'value2',
|
||||
type: 'text',
|
||||
enabled: true,
|
||||
secret: false
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"items": [
|
||||
{
|
||||
"items": [
|
||||
|
||||
55
packages/bruno-converters/tests/utils/flatten.spec.js
Normal file
55
packages/bruno-converters/tests/utils/flatten.spec.js
Normal file
@@ -0,0 +1,55 @@
|
||||
import { describe, it, expect } from '@jest/globals';
|
||||
import { flattenObject } from '../../src/utils/flatten';
|
||||
|
||||
describe('flattenObject', () => {
|
||||
it('returns empty object for empty input object', () => {
|
||||
expect(flattenObject({})).toEqual({});
|
||||
});
|
||||
|
||||
it('flattens a simple nested object', () => {
|
||||
const input = { user: { name: 'Tom', info: { id: 1 } } };
|
||||
expect(flattenObject(input)).toEqual({
|
||||
'user.name': 'Tom',
|
||||
'user.info.id': 1
|
||||
});
|
||||
});
|
||||
|
||||
it('flattens arrays using JavaScript-style square bracket notation', () => {
|
||||
const input = { tags: ['a', 'b'], nums: [1, 2] };
|
||||
expect(flattenObject(input)).toEqual({
|
||||
'tags[0]': 'a',
|
||||
'tags[1]': 'b',
|
||||
'nums[0]': 1,
|
||||
'nums[1]': 2
|
||||
});
|
||||
});
|
||||
|
||||
it('handles null and primitive leaves correctly', () => {
|
||||
const input = { a: null, b: true, c: 0, d: 'x' };
|
||||
expect(flattenObject(input)).toEqual({
|
||||
a: null,
|
||||
b: true,
|
||||
c: 0,
|
||||
d: 'x'
|
||||
});
|
||||
});
|
||||
|
||||
it('flattens mixed nested objects and arrays', () => {
|
||||
const input = {
|
||||
user: { name: 'Tom', roles: ['admin', 'editor'] },
|
||||
list: [{ id: 1 }, { id: 2 }]
|
||||
};
|
||||
expect(flattenObject(input)).toEqual({
|
||||
'user.name': 'Tom',
|
||||
'user.roles[0]': 'admin',
|
||||
'user.roles[1]': 'editor',
|
||||
'list[0].id': 1,
|
||||
'list[1].id': 2
|
||||
});
|
||||
});
|
||||
|
||||
it('ignores empty arrays/objects (no keys produced for empty containers)', () => {
|
||||
const input = { emptyObj: {}, emptyArr: [] };
|
||||
expect(flattenObject(input)).toEqual({});
|
||||
});
|
||||
});
|
||||
123
tests/import/insomnia/fixtures/insomnia-v4-with-envs.json
Normal file
123
tests/import/insomnia/fixtures/insomnia-v4-with-envs.json
Normal file
@@ -0,0 +1,123 @@
|
||||
{
|
||||
"_type": "export",
|
||||
"__export_format": 4,
|
||||
"__export_date": "2025-01-01T12:00:00.000Z",
|
||||
"__export_source": "insomnia.desktop.app:v10.3.1",
|
||||
"resources": [
|
||||
{
|
||||
"_id": "req_fdedb34f7d5541d0aa7a917ce37ec067",
|
||||
"parentId": "wrk_398c634c4fbc4774bcff39cbff44b31b",
|
||||
"modified": 1689952276171,
|
||||
"created": 1689951240510,
|
||||
"url": "{{baseUrl}}/api/users",
|
||||
"name": "Get Users",
|
||||
"description": "Fetch all users from the API",
|
||||
"method": "GET",
|
||||
"body": {},
|
||||
"parameters": [],
|
||||
"headers": [
|
||||
{
|
||||
"name": "Accept",
|
||||
"value": "application/json"
|
||||
}
|
||||
],
|
||||
"authentication": {},
|
||||
"metaSortKey": -1689951414329,
|
||||
"isPrivate": false,
|
||||
"settingStoreCookies": true,
|
||||
"settingSendCookies": true,
|
||||
"settingDisableRenderRequestBody": false,
|
||||
"settingEncodeUrl": true,
|
||||
"settingRebuildPath": true,
|
||||
"settingFollowRedirects": "global",
|
||||
"_type": "request"
|
||||
},
|
||||
{
|
||||
"_id": "wrk_398c634c4fbc4774bcff39cbff44b31b",
|
||||
"parentId": null,
|
||||
"modified": 1743678539806,
|
||||
"created": 1743678539806,
|
||||
"name": "Test API Collection v4 with Environments",
|
||||
"description": "Test collection for Insomnia v4 format with environments",
|
||||
"scope": "collection",
|
||||
"_type": "workspace"
|
||||
},
|
||||
{
|
||||
"_id": "env_93781eb62f074459bb67692112b76da0",
|
||||
"parentId": "wrk_398c634c4fbc4774bcff39cbff44b31b",
|
||||
"modified": 1743681240772,
|
||||
"created": 1689951235312,
|
||||
"name": "Base Environment",
|
||||
"data": {
|
||||
"baseUrl": "https://api.example.com",
|
||||
"authToken": "your_auth_token_here",
|
||||
"user": {
|
||||
"name": "admin",
|
||||
"id": 123,
|
||||
"roles": ["admin", "user"]
|
||||
},
|
||||
"config": {
|
||||
"timeout": 30000,
|
||||
"retries": 3,
|
||||
"debug": true
|
||||
}
|
||||
},
|
||||
"dataPropertyOrder": null,
|
||||
"color": null,
|
||||
"isPrivate": false,
|
||||
"metaSortKey": 1689951235312,
|
||||
"_type": "environment"
|
||||
},
|
||||
{
|
||||
"_id": "env_staging_123",
|
||||
"parentId": "env_93781eb62f074459bb67692112b76da0",
|
||||
"modified": 1743681240772,
|
||||
"created": 1689951235312,
|
||||
"name": "Staging",
|
||||
"data": {
|
||||
"baseUrl": "https://staging-api.example.com",
|
||||
"user": {
|
||||
"name": "staging_admin"
|
||||
},
|
||||
"config": {
|
||||
"timeout": 60000,
|
||||
"debug": false
|
||||
}
|
||||
},
|
||||
"dataPropertyOrder": null,
|
||||
"color": null,
|
||||
"isPrivate": false,
|
||||
"metaSortKey": 1689951235312,
|
||||
"_type": "environment"
|
||||
},
|
||||
{
|
||||
"_id": "env_dev_456",
|
||||
"parentId": "env_93781eb62f074459bb67692112b76da0",
|
||||
"modified": 1743681240772,
|
||||
"created": 1689951235312,
|
||||
"name": "Development",
|
||||
"data": {
|
||||
"baseUrl": "https://dev-api.example.com",
|
||||
"authToken": "dev_token_123",
|
||||
"newFeature": {
|
||||
"enabled": true,
|
||||
"version": 2.099123123
|
||||
}
|
||||
},
|
||||
"dataPropertyOrder": null,
|
||||
"color": null,
|
||||
"isPrivate": false,
|
||||
"metaSortKey": 1689951235312,
|
||||
"_type": "environment"
|
||||
},
|
||||
{
|
||||
"_id": "jar_09963a0322c24b698ecd2f866ae9a6ab",
|
||||
"parentId": "wrk_398c634c4fbc4774bcff39cbff44b31b",
|
||||
"modified": 1689951235313,
|
||||
"created": 1689951235313,
|
||||
"name": "Default Jar",
|
||||
"cookies": [],
|
||||
"_type": "cookie_jar"
|
||||
}
|
||||
]
|
||||
}
|
||||
87
tests/import/insomnia/fixtures/insomnia-v5-with-envs.yaml
Normal file
87
tests/import/insomnia/fixtures/insomnia-v5-with-envs.yaml
Normal file
@@ -0,0 +1,87 @@
|
||||
type: collection.insomnia.rest/5.0
|
||||
name: Test API Collection v5 with Environments
|
||||
meta:
|
||||
id: wrk_7faf891d273e4b7ea82bdbaa641ee17a
|
||||
created: 1743683067888
|
||||
modified: 1743683067888
|
||||
collection:
|
||||
- name: API Tests
|
||||
meta:
|
||||
id: fld_ab2a1533f2be48c194883bf07d693292
|
||||
created: 1743683080329
|
||||
modified: 1743683080329
|
||||
sortKey: -1743683080329
|
||||
children:
|
||||
- url: "{{ _.base_url }}/api/users"
|
||||
name: Get Users
|
||||
meta:
|
||||
id: req_0393b8ff4ee1454daddacdda33fd33ea
|
||||
created: 1743683426423
|
||||
modified: 1743683632735
|
||||
isPrivate: false
|
||||
sortKey: -1743683429031
|
||||
method: GET
|
||||
headers:
|
||||
- name: Authorization
|
||||
value: Bearer {{ _.auth_token }}
|
||||
settings:
|
||||
renderRequestBody: true
|
||||
encodeUrl: true
|
||||
followRedirects: global
|
||||
cookies:
|
||||
send: true
|
||||
store: true
|
||||
rebuildPath: true
|
||||
cookieJar:
|
||||
name: Default Jar
|
||||
meta:
|
||||
id: jar_25f97142fa796ae37f7f4937c0ebf3a07869d0a8
|
||||
created: 1743683067908
|
||||
modified: 1743683833282
|
||||
cookies: []
|
||||
environments:
|
||||
name: Base Environment
|
||||
meta:
|
||||
id: env_25f97142fa796ae37f7f4937c0ebf3a07869d0a8
|
||||
created: 1743683067895
|
||||
modified: 1743683476058
|
||||
isPrivate: false
|
||||
data:
|
||||
base_url: https://api.example.com
|
||||
auth_token: your_auth_token_here
|
||||
user:
|
||||
name: admin
|
||||
id: 123
|
||||
roles:
|
||||
- admin
|
||||
- user
|
||||
config:
|
||||
timeout: 30000
|
||||
retries: 3
|
||||
debug: true
|
||||
subEnvironments:
|
||||
- name: Staging
|
||||
meta:
|
||||
id: env_staging_123
|
||||
created: 1743683067895
|
||||
modified: 1743683476058
|
||||
isPrivate: false
|
||||
data:
|
||||
base_url: https://staging-api.example.com
|
||||
user:
|
||||
name: staging_admin
|
||||
config:
|
||||
timeout: 60000
|
||||
debug: false
|
||||
- name: Development
|
||||
meta:
|
||||
id: env_dev_456
|
||||
created: 1743683067895
|
||||
modified: 1743683476058
|
||||
isPrivate: false
|
||||
data:
|
||||
base_url: https://dev-api.example.com
|
||||
auth_token: dev_token_123
|
||||
new_feature:
|
||||
enabled: true
|
||||
version: 2.099123123
|
||||
185
tests/import/insomnia/import-insomnia-v4-environments.spec.ts
Normal file
185
tests/import/insomnia/import-insomnia-v4-environments.spec.ts
Normal file
@@ -0,0 +1,185 @@
|
||||
import { test, expect } from '../../../playwright';
|
||||
import * as path from 'path';
|
||||
import { openCollectionAndAcceptSandbox, closeAllCollections } from '../../utils/page/actions';
|
||||
|
||||
test.describe('Import Insomnia v4 Collection - Environment Import', () => {
|
||||
test.afterEach(async ({ page }) => {
|
||||
await closeAllCollections(page);
|
||||
});
|
||||
/**
|
||||
* Tests Insomnia v4 environment import with nested data flattening and environment merging.
|
||||
* Verifies that base and sub-environments are imported correctly with JavaScript-style keys
|
||||
* (e.g., user.name, user.roles[0]) and proper value inheritance/overrides.
|
||||
*
|
||||
* Test Structure:
|
||||
* - Base Environment: Contains nested objects, arrays, and primitive values
|
||||
* - Staging Environment: Overrides some base values, inherits others
|
||||
* - Development Environment: Adds new variables while inheriting base values
|
||||
*/
|
||||
test('Import Insomnia v4 collection with nested environments and verify flattening', async ({
|
||||
page,
|
||||
createTmpDir
|
||||
}) => {
|
||||
const insomniaFile = path.resolve(__dirname, 'fixtures', 'insomnia-v4-with-envs.json');
|
||||
|
||||
await test.step('Import Insomnia v4 collection with environments', async () => {
|
||||
await page.getByRole('button', { name: 'Import Collection' }).click();
|
||||
|
||||
const importModal = page.getByTestId('import-collection-modal');
|
||||
await importModal.waitFor({ state: 'visible' });
|
||||
await expect(importModal.locator('.bruno-modal-header-title')).toContainText('Import Collection');
|
||||
|
||||
await page.setInputFiles('input[type="file"]', insomniaFile);
|
||||
|
||||
await page.locator('#import-collection-loader').waitFor({ state: 'hidden' });
|
||||
|
||||
const locationModal = page.getByTestId('import-collection-location-modal');
|
||||
await expect(locationModal.getByText('Test API Collection v4 with Environments')).toBeVisible();
|
||||
|
||||
await page.locator('#collection-location').fill(await createTmpDir('insomnia-v4-env-test'));
|
||||
await page.getByRole('button', { name: 'Import', exact: true }).click();
|
||||
|
||||
await expect(page.locator('#sidebar-collection-name').getByText('Test API Collection v4 with Environments')).toBeVisible();
|
||||
|
||||
await openCollectionAndAcceptSandbox(page, 'Test API Collection v4 with Environments', 'safe');
|
||||
});
|
||||
|
||||
await test.step('Open collection environments panel', async () => {
|
||||
await page.getByTestId('environment-selector-trigger').click();
|
||||
await page.getByTestId('env-tab-collection').click();
|
||||
await page.getByRole('button', { name: 'Configure' }).click();
|
||||
});
|
||||
|
||||
await test.step('Verify all environments are present', async () => {
|
||||
await expect(page
|
||||
.locator('div')
|
||||
.filter({ hasText: /^Base Environment$/ })
|
||||
.first()).toBeVisible();
|
||||
await expect(page
|
||||
.locator('div')
|
||||
.filter({ hasText: /^Staging$/ })
|
||||
.first()).toBeVisible();
|
||||
await expect(page
|
||||
.locator('div')
|
||||
.filter({ hasText: /^Development$/ })
|
||||
.first()).toBeVisible();
|
||||
});
|
||||
|
||||
await test.step('Test Base Environment - verify flattened keys', async () => {
|
||||
await page
|
||||
.locator('div')
|
||||
.filter({ hasText: /^Base Environment$/ })
|
||||
.first()
|
||||
.click();
|
||||
|
||||
// **Assertion 1: Basic Variables (Top-level keys)**
|
||||
// Verifies that simple key-value pairs from the base environment are imported correctly
|
||||
const v4BaseUrlInput = page.locator('input[value="baseUrl"]');
|
||||
const v4AuthTokenInput = page.locator('input[value="authToken"]');
|
||||
await expect(v4BaseUrlInput).toBeVisible();
|
||||
await expect(v4AuthTokenInput).toBeVisible();
|
||||
|
||||
// Assert: Top-level string values are preserved exactly as in the source
|
||||
await expect(page.getByTestId('env-var-row-baseUrl').locator('.CodeMirror-line').first()).toHaveText('https://api.example.com');
|
||||
await expect(page.getByTestId('env-var-row-authToken').locator('.CodeMirror-line').first()).toHaveText('your_auth_token_here');
|
||||
|
||||
// **Assertion 2: Nested Object Flattening**
|
||||
// Verifies that nested objects are flattened to dot-notation keys (e.g., user.name, user.id)
|
||||
const v4UserNameInput = page.locator('input[value="user.name"]');
|
||||
const v4UserIdInput = page.locator('input[value="user.id"]');
|
||||
await expect(v4UserNameInput).toBeVisible();
|
||||
await expect(v4UserIdInput).toBeVisible();
|
||||
|
||||
// Assert: Nested object properties are accessible via dot notation
|
||||
await expect(page.getByTestId('env-var-row-user.name').locator('.CodeMirror-line').first()).toHaveText('admin');
|
||||
// Assert: Numeric values are converted to strings and preserved
|
||||
await expect(page.getByTestId('env-var-row-user.id').locator('.CodeMirror-line').first()).toHaveText('123');
|
||||
|
||||
// **Assertion 3: Array Flattening**
|
||||
// Verifies that arrays are flattened using JavaScript-style square bracket notation (e.g., user.roles[0], user.roles[1])
|
||||
const v4UserRoles0Input = page.locator('input[value="user.roles[0]"]');
|
||||
const v4UserRoles1Input = page.locator('input[value="user.roles[1]"]');
|
||||
await expect(v4UserRoles0Input).toBeVisible();
|
||||
await expect(v4UserRoles1Input).toBeVisible();
|
||||
|
||||
// Assert: Array elements are accessible via JavaScript-style square bracket notation
|
||||
await expect(page.getByTestId('env-var-row-user.roles[0]').locator('.CodeMirror-line').first()).toHaveText('admin');
|
||||
await expect(page.getByTestId('env-var-row-user.roles[1]').locator('.CodeMirror-line').first()).toHaveText('user');
|
||||
});
|
||||
|
||||
await test.step('Test Staging Environment - verify merging with base', async () => {
|
||||
await page
|
||||
.locator('div')
|
||||
.filter({ hasText: /^Staging$/ })
|
||||
.first()
|
||||
.click();
|
||||
|
||||
// **Assertion 1: Top-level Variable Override**
|
||||
// Verifies that staging environment overrides base environment values
|
||||
const v4StagingBaseUrlInput = page.locator('input[value="baseUrl"]');
|
||||
await expect(v4StagingBaseUrlInput).toBeVisible();
|
||||
// Assert: Staging overrides baseUrl with its own value
|
||||
await expect(page.getByTestId('env-var-row-baseUrl').locator('.CodeMirror-line').first()).toHaveText('https://staging-api.example.com');
|
||||
|
||||
// **Assertion 2: Top-level Variable Inheritance**
|
||||
// Verifies that staging environment inherits base environment values when not overridden
|
||||
const v4StagingAuthTokenInput = page.locator('input[value="authToken"]');
|
||||
await expect(v4StagingAuthTokenInput).toBeVisible();
|
||||
// Assert: Staging inherits authToken from base (not overridden in staging)
|
||||
await expect(page.getByTestId('env-var-row-authToken').locator('.CodeMirror-line').first()).toHaveText('your_auth_token_here');
|
||||
|
||||
// **Assertion 3: Nested Object Variable Override and Inheritance**
|
||||
// Verifies that nested object properties can be selectively overridden while inheriting others
|
||||
const v4StagingUserNameInput = page.locator('input[value="user.name"]');
|
||||
const v4StagingUserIdInput = page.locator('input[value="user.id"]');
|
||||
const v4StagingUserRoles0Input = page.locator('input[value="user.roles[0]"]');
|
||||
await expect(v4StagingUserNameInput).toBeVisible();
|
||||
await expect(v4StagingUserIdInput).toBeVisible();
|
||||
await expect(v4StagingUserRoles0Input).toBeVisible();
|
||||
|
||||
// Assert: Staging overrides user.name with its own value
|
||||
await expect(page.getByTestId('env-var-row-user.name').locator('.CodeMirror-line').first()).toHaveText('staging_admin');
|
||||
// Assert: Staging inherits user.id from base (not overridden in staging)
|
||||
await expect(page.getByTestId('env-var-row-user.id').locator('.CodeMirror-line').first()).toHaveText('123');
|
||||
// Assert: Staging inherits user.roles[0] from base (not overridden in staging)
|
||||
await expect(page.getByTestId('env-var-row-user.roles[0]').locator('.CodeMirror-line').first()).toHaveText('admin');
|
||||
});
|
||||
|
||||
await test.step('Test Development Environment - verify new variables', async () => {
|
||||
await page
|
||||
.locator('div')
|
||||
.filter({ hasText: /^Development$/ })
|
||||
.first()
|
||||
.click();
|
||||
|
||||
// **Assertion 1: Multiple Top-level Variable Overrides**
|
||||
// Verifies that development environment can override multiple base environment values
|
||||
const v4DevBaseUrlInput = page.locator('input[value="baseUrl"]');
|
||||
const v4DevAuthTokenInput = page.locator('input[value="authToken"]');
|
||||
await expect(v4DevBaseUrlInput).toBeVisible();
|
||||
await expect(v4DevAuthTokenInput).toBeVisible();
|
||||
|
||||
// Assert: Development overrides baseUrl with its own value
|
||||
await expect(page.getByTestId('env-var-row-baseUrl').locator('.CodeMirror-line').first()).toHaveText('https://dev-api.example.com');
|
||||
// Assert: Development overrides authToken with its own value
|
||||
await expect(page.getByTestId('env-var-row-authToken').locator('.CodeMirror-line').first()).toHaveText('dev_token_123');
|
||||
|
||||
// **Assertion 2: New Nested Variables Addition**
|
||||
// Verifies that development environment can add completely new nested variables not present in base
|
||||
const v4NewFeatureEnabledInput = page.locator('input[value="newFeature.enabled"]');
|
||||
const v4NewFeatureVersionInput = page.locator('input[value="newFeature.version"]');
|
||||
await expect(v4NewFeatureEnabledInput).toBeVisible();
|
||||
await expect(v4NewFeatureVersionInput).toBeVisible();
|
||||
|
||||
// Assert: New boolean variable is added and converted to string
|
||||
await expect(page.getByTestId('env-var-row-newFeature.enabled').locator('.CodeMirror-line').first()).toHaveText('true');
|
||||
// Assert: New numeric variable is added and converted to string with full precision
|
||||
await expect(page.getByTestId('env-var-row-newFeature.version').locator('.CodeMirror-line').first()).toHaveText('2.099123123');
|
||||
});
|
||||
|
||||
await test.step('Close environment modal', async () => {
|
||||
// Close the environment configuration modal to ensure clean state
|
||||
await page.getByText('×').click();
|
||||
});
|
||||
});
|
||||
});
|
||||
212
tests/import/insomnia/import-insomnia-v5-environments.spec.ts
Normal file
212
tests/import/insomnia/import-insomnia-v5-environments.spec.ts
Normal file
@@ -0,0 +1,212 @@
|
||||
import { test, expect } from '../../../playwright';
|
||||
import * as path from 'path';
|
||||
import { openCollectionAndAcceptSandbox, closeAllCollections } from '../../utils/page/actions';
|
||||
|
||||
test.describe('Import Insomnia v5 Collection - Environment Import', () => {
|
||||
test.afterEach(async ({ page }) => {
|
||||
await closeAllCollections(page);
|
||||
});
|
||||
/**
|
||||
* Tests Insomnia v5 environment import with nested data flattening and environment merging.
|
||||
* Verifies that base and sub-environments are imported correctly with JavaScript-style keys
|
||||
* (e.g., user.name, user.roles[0]) and proper value inheritance/overrides.
|
||||
*
|
||||
* Test Structure:
|
||||
* - Base Environment: Contains nested objects, arrays, and primitive values
|
||||
* - Staging Environment: Overrides some base values, inherits others
|
||||
* - Development Environment: Adds new variables while inheriting base values
|
||||
*/
|
||||
test('Import Insomnia v5 collection with nested environments and verify flattening', async ({
|
||||
page,
|
||||
createTmpDir
|
||||
}) => {
|
||||
const insomniaFile = path.resolve(__dirname, 'fixtures', 'insomnia-v5-with-envs.yaml');
|
||||
|
||||
await test.step('Import Insomnia v5 collection with environments', async () => {
|
||||
await page.getByRole('button', { name: 'Import Collection' }).click();
|
||||
|
||||
const importModal = page.getByTestId('import-collection-modal');
|
||||
await importModal.waitFor({ state: 'visible' });
|
||||
await expect(importModal.locator('.bruno-modal-header-title')).toContainText('Import Collection');
|
||||
|
||||
await page.setInputFiles('input[type="file"]', insomniaFile);
|
||||
|
||||
await page.locator('#import-collection-loader').waitFor({ state: 'hidden' });
|
||||
|
||||
const locationModal = page.getByTestId('import-collection-location-modal');
|
||||
await expect(locationModal.getByText('Test API Collection v5 with Environments')).toBeVisible();
|
||||
|
||||
await page.locator('#collection-location').fill(await createTmpDir('insomnia-v5-env-test'));
|
||||
await page.getByRole('button', { name: 'Import', exact: true }).click();
|
||||
|
||||
await expect(page.getByText('Test API Collection v5 with Environments')).toBeVisible();
|
||||
|
||||
await openCollectionAndAcceptSandbox(page, 'Test API Collection v5 with Environments', 'safe');
|
||||
});
|
||||
|
||||
await test.step('Open collection environments panel', async () => {
|
||||
await page.getByTestId('environment-selector-trigger').click();
|
||||
await page.getByTestId('env-tab-collection').click();
|
||||
await page.getByRole('button', { name: 'Configure' }).click();
|
||||
});
|
||||
|
||||
await test.step('Verify all environments are present', async () => {
|
||||
await expect(page
|
||||
.locator('div')
|
||||
.filter({ hasText: /^Base Environment$/ })
|
||||
.first()).toBeVisible();
|
||||
await expect(page
|
||||
.locator('div')
|
||||
.filter({ hasText: /^Staging$/ })
|
||||
.first()).toBeVisible();
|
||||
await expect(page
|
||||
.locator('div')
|
||||
.filter({ hasText: /^Development$/ })
|
||||
.first()).toBeVisible();
|
||||
});
|
||||
|
||||
await test.step('Test Base Environment - verify flattened keys', async () => {
|
||||
await page
|
||||
.locator('div')
|
||||
.filter({ hasText: /^Base Environment$/ })
|
||||
.first()
|
||||
.click();
|
||||
|
||||
// **Assertion 1: Basic Variables (Top-level keys)**
|
||||
// Verifies that simple key-value pairs from the base environment are imported correctly
|
||||
const baseUrlInput = page.locator('input[value="base_url"]');
|
||||
const authTokenInput = page.locator('input[value="auth_token"]');
|
||||
await expect(baseUrlInput).toBeVisible();
|
||||
await expect(authTokenInput).toBeVisible();
|
||||
|
||||
// Assert: Top-level string values are preserved exactly as in the source
|
||||
await expect(page.getByTestId('env-var-row-base_url').locator('.CodeMirror-line').first()).toHaveText('https://api.example.com');
|
||||
await expect(page.getByTestId('env-var-row-auth_token').locator('.CodeMirror-line').first()).toHaveText('your_auth_token_here');
|
||||
|
||||
// **Assertion 2: Nested Object Flattening**
|
||||
// Verifies that nested objects are flattened to dot-notation keys (e.g., user.name, user.id)
|
||||
const userNameInput = page.locator('input[value="user.name"]');
|
||||
const userIdInput = page.locator('input[value="user.id"]');
|
||||
await expect(userNameInput).toBeVisible();
|
||||
await expect(userIdInput).toBeVisible();
|
||||
|
||||
// Assert: Nested object properties are accessible via dot notation
|
||||
await expect(page.getByTestId('env-var-row-user.name').locator('.CodeMirror-line').first()).toHaveText('admin');
|
||||
// Assert: Numeric values are converted to strings and preserved
|
||||
await expect(page.getByTestId('env-var-row-user.id').locator('.CodeMirror-line').first()).toHaveText('123');
|
||||
|
||||
// **Assertion 3: Array Flattening**
|
||||
// Verifies that arrays are flattened using JavaScript-style square bracket notation (e.g., user.roles[0], user.roles[1])
|
||||
const userRoles0Input = page.locator('input[value="user.roles[0]"]');
|
||||
const userRoles1Input = page.locator('input[value="user.roles[1]"]');
|
||||
await expect(userRoles0Input).toBeVisible();
|
||||
await expect(userRoles1Input).toBeVisible();
|
||||
|
||||
// Assert: Array elements are accessible via JavaScript-style square bracket notation
|
||||
await expect(page.getByTestId('env-var-row-user.roles[0]').locator('.CodeMirror-line').first()).toHaveText('admin');
|
||||
await expect(page.getByTestId('env-var-row-user.roles[1]').locator('.CodeMirror-line').first()).toHaveText('user');
|
||||
|
||||
// **Assertion 4: Deeply Nested Config Objects**
|
||||
// Verifies that deeply nested objects are properly flattened (e.g., config.timeout, config.debug)
|
||||
const configTimeoutInput = page.locator('input[value="config.timeout"]');
|
||||
const configDebugInput = page.locator('input[value="config.debug"]');
|
||||
await expect(configTimeoutInput).toBeVisible();
|
||||
await expect(configDebugInput).toBeVisible();
|
||||
|
||||
// Assert: Numeric values in nested objects are converted to strings
|
||||
await expect(page.getByTestId('env-var-row-config.timeout').locator('.CodeMirror-line').first()).toHaveText('30000');
|
||||
// Assert: Boolean values in nested objects are converted to strings
|
||||
await expect(page.getByTestId('env-var-row-config.debug').locator('.CodeMirror-line').first()).toHaveText('true');
|
||||
});
|
||||
|
||||
await test.step('Test Staging Environment - verify merging and overrides', async () => {
|
||||
await page
|
||||
.locator('div')
|
||||
.filter({ hasText: /^Staging$/ })
|
||||
.first()
|
||||
.click();
|
||||
|
||||
// **Assertion 1: Top-level Variable Override**
|
||||
// Verifies that staging environment overrides base environment values
|
||||
const stagingBaseUrlInput = page.locator('input[value="base_url"]');
|
||||
await expect(stagingBaseUrlInput).toBeVisible();
|
||||
// Assert: Staging overrides base_url with its own value
|
||||
await expect(page.getByTestId('env-var-row-base_url').locator('.CodeMirror-line').first()).toHaveText('https://staging-api.example.com');
|
||||
|
||||
// **Assertion 2: Top-level Variable Inheritance**
|
||||
// Verifies that staging environment inherits base environment values when not overridden
|
||||
const stagingAuthTokenInput = page.locator('input[value="auth_token"]');
|
||||
await expect(stagingAuthTokenInput).toBeVisible();
|
||||
// Assert: Staging inherits auth_token from base (not overridden in staging)
|
||||
await expect(page.getByTestId('env-var-row-auth_token').locator('.CodeMirror-line').first()).toHaveText('your_auth_token_here');
|
||||
|
||||
// **Assertion 3: Nested Object Variable Override and Inheritance**
|
||||
// Verifies that nested object properties can be selectively overridden while inheriting others
|
||||
const stagingUserNameInput = page.locator('input[value="user.name"]');
|
||||
const stagingUserIdInput = page.locator('input[value="user.id"]');
|
||||
await expect(stagingUserNameInput).toBeVisible();
|
||||
await expect(stagingUserIdInput).toBeVisible();
|
||||
|
||||
// Assert: Staging overrides user.name with its own value
|
||||
await expect(page.getByTestId('env-var-row-user.name').locator('.CodeMirror-line').first()).toHaveText('staging_admin');
|
||||
// Assert: Staging inherits user.id from base (not overridden in staging)
|
||||
await expect(page.getByTestId('env-var-row-user.id').locator('.CodeMirror-line').first()).toHaveText('123');
|
||||
|
||||
// **Assertion 4: Deeply Nested Config Override**
|
||||
// Verifies that deeply nested object properties can be overridden
|
||||
const stagingConfigTimeoutInput = page.locator('input[value="config.timeout"]');
|
||||
const stagingConfigDebugInput = page.locator('input[value="config.debug"]');
|
||||
await expect(stagingConfigTimeoutInput).toBeVisible();
|
||||
await expect(stagingConfigDebugInput).toBeVisible();
|
||||
|
||||
// Assert: Staging overrides config.timeout with its own value
|
||||
await expect(page.getByTestId('env-var-row-config.timeout').locator('.CodeMirror-line').first()).toHaveText('60000');
|
||||
// Assert: Staging overrides config.debug with its own value
|
||||
await expect(page.getByTestId('env-var-row-config.debug').locator('.CodeMirror-line').first()).toHaveText('false');
|
||||
});
|
||||
|
||||
await test.step('Test Development Environment - verify new variables', async () => {
|
||||
await page
|
||||
.locator('div')
|
||||
.filter({ hasText: /^Development$/ })
|
||||
.first()
|
||||
.click();
|
||||
|
||||
// **Assertion 1: Multiple Top-level Variable Overrides**
|
||||
// Verifies that development environment can override multiple base environment values
|
||||
const devBaseUrlInput = page.locator('input[value="base_url"]');
|
||||
const devAuthTokenInput = page.locator('input[value="auth_token"]');
|
||||
await expect(devBaseUrlInput).toBeVisible();
|
||||
await expect(devAuthTokenInput).toBeVisible();
|
||||
|
||||
// Assert: Development overrides base_url with its own value
|
||||
await expect(page.getByTestId('env-var-row-base_url').locator('.CodeMirror-line').first()).toHaveText('https://dev-api.example.com');
|
||||
// Assert: Development overrides auth_token with its own value
|
||||
await expect(page.getByTestId('env-var-row-auth_token').locator('.CodeMirror-line').first()).toHaveText('dev_token_123');
|
||||
|
||||
// **Assertion 2: New Nested Variables Addition**
|
||||
// Verifies that development environment can add completely new nested variables not present in base
|
||||
const newFeatureEnabledInput = page.locator('input[value="new_feature.enabled"]');
|
||||
const newFeatureVersionInput = page.locator('input[value="new_feature.version"]');
|
||||
await expect(newFeatureEnabledInput).toBeVisible();
|
||||
await expect(newFeatureVersionInput).toBeVisible();
|
||||
|
||||
// Assert: New boolean variable is added and converted to string
|
||||
await expect(page.getByTestId('env-var-row-new_feature.enabled').locator('.CodeMirror-line').first()).toHaveText('true');
|
||||
// Assert: New numeric variable is added and converted to string with full precision
|
||||
await expect(page.getByTestId('env-var-row-new_feature.version').locator('.CodeMirror-line').first()).toHaveText('2.099123123');
|
||||
|
||||
// **Assertion 3: Base Variable Inheritance**
|
||||
// Verifies that development environment still inherits base variables that are not overridden
|
||||
const devUserRoles0Input = page.locator('input[value="user.roles[0]"]');
|
||||
await expect(devUserRoles0Input).toBeVisible();
|
||||
// Assert: Development inherits user.roles[0] from base (not overridden in development)
|
||||
await expect(page.getByTestId('env-var-row-user.roles[0]').locator('.CodeMirror-line').first()).toHaveText('admin');
|
||||
});
|
||||
|
||||
await test.step('Close environment modal', async () => {
|
||||
// Close the environment configuration modal to ensure clean state
|
||||
await page.getByText('×').click();
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user