Merge branch 'main' into add-feature-scripts

This commit is contained in:
Ankit Mahato
2025-06-21 20:39:25 +05:30
committed by GitHub
27 changed files with 314 additions and 60 deletions

26
SECURITY.md Normal file
View File

@ -0,0 +1,26 @@
# Security Policy
This document describes the management of vulnerabilities for API Dash project & the Dart/Flutter packages in the repository.
## Preferred Languages
We prefer all communications to be in English.
## Reporting a Vulnerability
**Please do not report security vulnerabilities through public GitHub issues.**
Individuals who find potential vulnerabilities in API Dash and Dart/Flutter packages in the API Dash repository are invited to [open a draft security advisory](https://github.com/foss42/apidash/security/advisories/new) for discussion and collaboration on the fix.
Please include the requested information listed below (as much as you can provide) to help us better understand the nature and scope of the possible issue:
- Type of issue (e.g. buffer overflow, poisoned dependency, cross-site scripting, etc.)
- Full paths of source file(s) related to the manifestation of the issue
- The location of the affected source code (tag/branch/commit or direct URL)
- Any special configuration required to reproduce the issue
- Step-by-step instructions to reproduce the issue
- Proof-of-concept or exploit code (if possible)
- Impact of the issue, including how an attacker might exploit the issue
This information will help us triage your report more quickly.
Our team will positivey respond to any reported vulnerability and take swift action to resolve it.

View File

@ -0,0 +1 @@

View File

@ -0,0 +1 @@

View File

@ -1,4 +1,3 @@
import 'dart:convert';
import '../services/dashbot_service.dart';
import 'package:apidash/models/request_model.dart';
@ -19,20 +18,14 @@ class ExplainFeature {
return "Error: Invalid API request (missing endpoint).";
}
final method = requestModel.httpRequestModel?.method
.toString()
.split('.')
.last
.toUpperCase() ??
"GET";
final endpoint = requestModel.httpRequestModel!.url;
final method =
requestModel.httpRequestModel?.method.name.toUpperCase() ?? "GET";
final url = requestModel.httpRequestModel!.url;
final headers = requestModel.httpRequestModel?.enabledHeadersMap ?? {};
final parameters = requestModel.httpRequestModel?.enabledParamsMap ?? {};
final body = requestModel.httpRequestModel?.body;
final rawResponse = responseModel.body;
final responseBody =
rawResponse is String ? rawResponse : jsonEncode(rawResponse);
final statusCode = responseModel.statusCode ?? 0;
final body = requestModel.httpRequestModel?.body ?? '';
final responseBody = responseModel.body;
final statusCode = responseModel.statusCode;
final prompt = '''
FOCUSED API INTERACTION BREAKDOWN
@ -41,10 +34,16 @@ FOCUSED API INTERACTION BREAKDOWN
- Endpoint Purpose: What is this API endpoint designed to do?
- Interaction Type: Describe the core purpose of this specific request
**Request Mechanics:**
- Exact Endpoint: $endpoint
**Request Details:**
- Endpoint: $url
- HTTP Method: $method
- Key Parameters: ${parameters.isNotEmpty ? 'Specific inputs driving the request' : 'No custom parameters'}
- Request Headers: ${headers.isEmpty ? "None" : headers}
- URL Parameters: ${parameters.isEmpty ? "None" : parameters}
- Request Body: ${body.isEmpty ? "None" : body}
**Response Details**
- Status Code: $statusCode
- Content: $responseBody
**Response CORE Insights:**
- Status: Success or Failure?

View File

@ -17,6 +17,7 @@ class SettingsModel {
this.historyRetentionPeriod = HistoryRetentionPeriod.oneWeek,
this.workspaceFolderPath,
this.isSSLDisabled = false,
this.isDashBotEnabled = true,
});
final bool isDark;
@ -31,6 +32,7 @@ class SettingsModel {
final HistoryRetentionPeriod historyRetentionPeriod;
final String? workspaceFolderPath;
final bool isSSLDisabled;
final bool isDashBotEnabled;
SettingsModel copyWith({
bool? isDark,
@ -45,6 +47,7 @@ class SettingsModel {
HistoryRetentionPeriod? historyRetentionPeriod,
String? workspaceFolderPath,
bool? isSSLDisabled,
bool? isDashBotEnabled,
}) {
return SettingsModel(
isDark: isDark ?? this.isDark,
@ -61,6 +64,7 @@ class SettingsModel {
historyRetentionPeriod ?? this.historyRetentionPeriod,
workspaceFolderPath: workspaceFolderPath ?? this.workspaceFolderPath,
isSSLDisabled: isSSLDisabled ?? this.isSSLDisabled,
isDashBotEnabled: isDashBotEnabled ?? this.isDashBotEnabled,
);
}
@ -80,6 +84,7 @@ class SettingsModel {
historyRetentionPeriod: historyRetentionPeriod,
workspaceFolderPath: workspaceFolderPath,
isSSLDisabled: isSSLDisabled,
isDashBotEnabled: isDashBotEnabled,
);
}
@ -134,6 +139,7 @@ class SettingsModel {
}
final workspaceFolderPath = data["workspaceFolderPath"] as String?;
final isSSLDisabled = data["isSSLDisabled"] as bool?;
final isDashBotEnabled = data["isDashBotEnabled"] as bool?;
const sm = SettingsModel();
@ -151,6 +157,7 @@ class SettingsModel {
historyRetentionPeriod ?? HistoryRetentionPeriod.oneWeek,
workspaceFolderPath: workspaceFolderPath,
isSSLDisabled: isSSLDisabled,
isDashBotEnabled: isDashBotEnabled,
);
}
@ -170,6 +177,7 @@ class SettingsModel {
"historyRetentionPeriod": historyRetentionPeriod.name,
"workspaceFolderPath": workspaceFolderPath,
"isSSLDisabled": isSSLDisabled,
"isDashBotEnabled": isDashBotEnabled,
};
}
@ -194,7 +202,8 @@ class SettingsModel {
other.activeEnvironmentId == activeEnvironmentId &&
other.historyRetentionPeriod == historyRetentionPeriod &&
other.workspaceFolderPath == workspaceFolderPath &&
other.isSSLDisabled == isSSLDisabled;
other.isSSLDisabled == isSSLDisabled &&
other.isDashBotEnabled == isDashBotEnabled;
}
@override
@ -213,6 +222,7 @@ class SettingsModel {
historyRetentionPeriod,
workspaceFolderPath,
isSSLDisabled,
isDashBotEnabled,
);
}
}

View File

@ -33,6 +33,7 @@ class ThemeStateNotifier extends StateNotifier<SettingsModel> {
HistoryRetentionPeriod? historyRetentionPeriod,
String? workspaceFolderPath,
bool? isSSLDisabled,
bool? isDashBotEnabled,
}) async {
state = state.copyWith(
isDark: isDark,
@ -47,6 +48,7 @@ class ThemeStateNotifier extends StateNotifier<SettingsModel> {
historyRetentionPeriod: historyRetentionPeriod,
workspaceFolderPath: workspaceFolderPath,
isSSLDisabled: isSSLDisabled,
isDashBotEnabled: isDashBotEnabled,
);
await setSettingsToSharedPrefs(state);
}

View File

@ -1,3 +1,4 @@
import 'package:apidash/consts.dart';
import 'package:apidash_design_system/apidash_design_system.dart';
import 'package:flutter/material.dart';
import 'package:multi_trigger_autocomplete_plus/multi_trigger_autocomplete_plus.dart';
@ -37,6 +38,8 @@ class EnvCellField extends StatelessWidget {
decoration: getTextFieldInputDecoration(
clrScheme,
hintText: hintText,
isDense: true,
contentPadding: kIsMobile ? kPh6b12 : null,
),
autocompleteNoTrigger: autocompleteNoTrigger,
onChanged: onChanged,

View File

@ -17,6 +17,7 @@ class Dashboard extends ConsumerWidget {
@override
Widget build(BuildContext context, WidgetRef ref) {
final railIdx = ref.watch(navRailIndexStateProvider);
final settings = ref.watch(settingsProvider);
return Scaffold(
body: SafeArea(
child: Row(
@ -125,18 +126,19 @@ class Dashboard extends ConsumerWidget {
],
),
),
// TODO: Release DashBot
// floatingActionButton: FloatingActionButton(
// onPressed: () => showModalBottomSheet(
// context: context,
// isScrollControlled: true,
// builder: (context) => const Padding(
// padding: EdgeInsets.all(16.0),
// child: DashBotWidget(),
// ),
// ),
// child: const Icon(Icons.help_outline),
// ),
floatingActionButton: settings.isDashBotEnabled
? FloatingActionButton(
onPressed: () => showModalBottomSheet(
context: context,
isScrollControlled: true,
builder: (context) => const Padding(
padding: EdgeInsets.all(16.0),
child: DashBotWidget(),
),
),
child: const Icon(Icons.help_outline),
)
: null,
);
}
}

View File

@ -50,6 +50,18 @@ class SettingsPage extends ConsumerWidget {
ref.read(settingsProvider.notifier).update(isDark: value);
},
),
ADListTile(
type: ListTileType.switchOnOff,
title: 'DashBot',
subtitle:
'Current selection: ${settings.isDashBotEnabled ? "Enabled" : "Disabled"}',
value: settings.isDashBotEnabled,
onChanged: (bool? value) {
ref
.read(settingsProvider.notifier)
.update(isDashBotEnabled: value);
},
),
ADListTile(
type: ListTileType.switchOnOff,
title: 'Collection Pane Scrollbar Visiblity',

View File

@ -30,6 +30,8 @@ final kSupportedUriSchemes =
SupportedUriSchemes.values.map((i) => i.name).toList();
const kDefaultUriScheme = SupportedUriSchemes.https;
final kLocalhostRegex = RegExp(r'^localhost(:\d+)?(/.*)?$');
final kIPHostRegex =
RegExp(r'^((25[0-5]|(2[0-4]|1\d|[1-9]|)\d)\.?\b){4}(:\d+)?(/.*)?$');
const kMethodsWithBody = [
HTTPVerb.post,

View File

@ -2,6 +2,7 @@ import 'package:apidash_core/consts.dart';
import 'package:seed/seed.dart';
import '../models/models.dart';
import 'graphql_utils.dart';
import 'package:json5/json5.dart' as json5;
Map<String, String>? rowsToMap(
List<NameValueModel>? kvRows, {
@ -100,3 +101,14 @@ String? getRequestBody(APIType type, HttpRequestModel httpRequestModel) {
APIType.graphql => getGraphQLBody(httpRequestModel),
};
}
// TODO: Expose this function to remove JSON comments
String? removeJsonComments(String? json) {
try {
if (json == null) return null;
var parsed = json5.json5Decode(json);
return kJsonEncoder.convert(parsed);
} catch (e) {
return json;
}
}

View File

@ -30,9 +30,10 @@ String stripUrlParams(String url) {
return (null, "URL is missing!");
}
if (kLocalhostRegex.hasMatch(url)) {
if (kLocalhostRegex.hasMatch(url) || kIPHostRegex.hasMatch(url)) {
url = '${SupportedUriSchemes.http.name}://$url';
}
Uri? uri = Uri.tryParse(url);
if (uri == null) {
return (null, "Check URL (malformed)");

View File

@ -19,6 +19,7 @@ dependencies:
http_parser: ^4.1.2
insomnia_collection:
path: ../insomnia_collection
json5: ^0.8.2
postman:
path: ../postman
seed: ^0.0.3

View File

@ -0,0 +1,74 @@
import 'package:apidash_core/utils/http_request_utils.dart';
import 'package:test/test.dart';
void main() {
group('Testing RemoveJsonComments', () {
test('Removes single-line comments', () {
String input = '''
{
// This is a single-line comment
"key": "value"
}
''';
String expected = '''{
"key": "value"
}''';
expect(removeJsonComments(input), expected);
});
test('Removes multi-line comments', () {
String input = '''
{
/*
This is a multi-line comment
*/
"key": "value"
}
''';
String expected = '''{
"key": "value"
}''';
expect(removeJsonComments(input), expected);
});
test('Handles valid JSON without comments', () {
String input = '{"key":"value"}';
String expected = '''{
"key": "value"
}''';
expect(removeJsonComments(input), expected);
});
test('Returns original string if invalid JSON', () {
String input = '{key: value}';
String expected = '{key: value}';
expect(removeJsonComments(input), expected);
});
test('Removes trailing commas', () {
String input = '''
{
"key1": "value1",
"key2": "value2", // trailing comma
}
''';
String expected = '''{
"key1": "value1",
"key2": "value2"
}''';
expect(removeJsonComments(input), expected);
});
test('Test blank json', () {
String input = '''
{}
''';
String expected = '{}';
expect(removeJsonComments(input), expected);
});
});
}

View File

@ -62,6 +62,37 @@ void main() {
expect(getValidRequestUri(url1, []), (uri1Expected, null));
});
test('Testing getValidRequestUri with IP URL without port or path', () {
String url1 = "8.8.8.8";
Uri uri1Expected = Uri(scheme: 'http', host: '8.8.8.8');
expect(getValidRequestUri(url1, []), (uri1Expected, null));
});
test('Testing getValidRequestUri with IP URL with port', () {
String url1 = "8.8.8.8:8080";
Uri uri1Expected = Uri(scheme: 'http', host: '8.8.8.8', port: 8080);
expect(getValidRequestUri(url1, []), (uri1Expected, null));
});
test('Testing getValidRequestUri with IP URL with port and path', () {
String url1 = "8.8.8.8:8080/hello";
Uri uri1Expected =
Uri(scheme: 'http', host: '8.8.8.8', port: 8080, path: '/hello');
expect(getValidRequestUri(url1, []), (uri1Expected, null));
});
test('Testing getValidRequestUri with IP URL with http prefix', () {
String url1 = "http://8.8.8.8:3080";
Uri uri1Expected = Uri(scheme: 'http', host: '8.8.8.8', port: 3080);
expect(getValidRequestUri(url1, []), (uri1Expected, null));
});
test('Testing getValidRequestUri with IP URL with https prefix', () {
String url1 = "https://8.8.8.8:8080";
Uri uri1Expected = Uri(scheme: 'https', host: '8.8.8.8', port: 8080);
expect(getValidRequestUri(url1, []), (uri1Expected, null));
});
test('Testing getValidRequestUri for normal values', () {
String url1 = "https://api.apidash.dev/country/data";
const kvRow1 = NameValueModel(name: "code", value: "US");

View File

@ -64,6 +64,11 @@ const kPs0o6 = EdgeInsets.only(
right: 6,
bottom: 6,
);
const kPh6b12 = EdgeInsets.only(
left: 6.0,
right: 6.0,
bottom: 12.0,
);
const kPh60 = EdgeInsets.symmetric(horizontal: 60);
const kPh60v60 = EdgeInsets.symmetric(vertical: 60, horizontal: 60);
const kPt24l4 = EdgeInsets.only(

View File

@ -1,3 +1,7 @@
## 0.1.3
- Bugfix: Header with `:` in value gets parsed properly
## 0.1.2
- Bump dependencies.

View File

@ -130,10 +130,15 @@ class Curl extends Equatable {
headers = <String, String>{};
for (var headerString in headersList) {
final splittedHeaderString = headerString.split(RegExp(r':\s*'));
if (splittedHeaderString.length != 2) {
if (splittedHeaderString.length > 2) {
headers.addAll({
splittedHeaderString[0]: splittedHeaderString.sublist(1).join(":")
});
} else if (splittedHeaderString.length < 2) {
throw Exception('Failed to split the `$headerString` header');
} else {
headers.addAll({splittedHeaderString[0]: splittedHeaderString[1]});
}
headers.addAll({splittedHeaderString[0]: splittedHeaderString[1]});
}
}
}

View File

@ -1,6 +1,6 @@
name: curl_parser
description: Parse cURL command to Dart object and convert Dart object to cURL command.
version: 0.1.2
version: 0.1.3
homepage: https://github.com/foss42/apidash/tree/main/packages/curl_parser
repository: https://github.com/foss42/apidash/tree/main/packages/curl_parser
issue_tracker: https://github.com/foss42/apidash/issues

View File

@ -19,6 +19,30 @@ void main() {
},
);
test(
'parse another easy cURL',
() async {
expect(
Curl.parse(
r"""curl --location --request GET 'https://dummyimage.com/150/92c952' \
--header 'user-agent: Dart/3.8 (dart:io)' \
--header 'accept-encoding: gzip' \
--header 'content-length: 0' \
--header 'host: dummyimage.com'"""),
Curl(
method: 'GET',
uri: Uri.parse('https://dummyimage.com/150/92c952'),
headers: {
'user-agent': 'Dart/3.8 (dart:io)',
'accept-encoding': 'gzip',
'content-length': '0',
'host': 'dummyimage.com'
},
location: true),
);
},
);
test('parse POST request with multipart/form-data', () {
const curl = r'''curl -X POST 'https://api.apidash.dev/io/img' \
-H 'Content-Type: multipart/form-data' \

View File

@ -32,6 +32,30 @@ void main() {
);
}, timeout: defaultTimeout);
test('parse cURL DevTools', () async {
expect(
splitAsCommandLineArgs(
r"""--request GET 'https://dummyimage.com/150/92c952' \
--header 'user-agent: Dart/3.8 (dart:io)' \
--header 'accept-encoding: gzip' \
--header 'content-length: 0' \
--header 'host: dummyimage.com'"""),
[
'--request',
'GET',
'https://dummyimage.com/150/92c952',
'--header',
'user-agent: Dart/3.8 (dart:io)',
'--header',
'accept-encoding: gzip',
'--header',
'content-length: 0',
'--header',
'host: dummyimage.com'
],
);
}, timeout: defaultTimeout);
test('parse cURL with body', () async {
expect(
splitAsCommandLineArgs(r"""--request POST \

View File

@ -5,10 +5,10 @@ packages:
dependency: transitive
description:
name: async
sha256: d2872f9c19731c2e5f10444b14686eb7cc85c76274bd6c16e1816bff9a3bab63
sha256: "758e6d74e971c3e5aceb4110bfd6698efc7f501675bcfe0c775459a8140750eb"
url: "https://pub.dev"
source: hosted
version: "2.12.0"
version: "2.13.0"
boolean_selector:
dependency: transitive
description:
@ -69,10 +69,10 @@ packages:
dependency: transitive
description:
name: fake_async
sha256: "6a95e56b2449df2273fd8c45a662d6947ce1ebb7aafe80e550a3f68297f3cacc"
sha256: "5368f224a74523e8d2e7399ea1638b37aecfca824a3cc4dfdf77bf1fa905ac44"
url: "https://pub.dev"
source: hosted
version: "1.3.2"
version: "1.3.3"
flutter:
dependency: "direct main"
description: flutter
@ -102,10 +102,10 @@ packages:
dependency: transitive
description:
name: leak_tracker
sha256: c35baad643ba394b40aac41080300150a4f08fd0fd6a10378f8f7c6bc161acec
sha256: "6bb818ecbdffe216e81182c2f0714a2e62b593f4a4f13098713ff1685dfb6ab0"
url: "https://pub.dev"
source: hosted
version: "10.0.8"
version: "10.0.9"
leak_tracker_flutter_testing:
dependency: transitive
description:
@ -227,10 +227,10 @@ packages:
dependency: transitive
description:
name: vm_service
sha256: "0968250880a6c5fe7edc067ed0a13d4bae1577fe2771dcf3010d52c4a9d3ca14"
sha256: ddfa8d30d89985b96407efce8acbdd124701f96741f2d981ca860662f1c0dc02
url: "https://pub.dev"
source: hosted
version: "14.3.1"
version: "15.0.0"
sdks:
dart: ">=3.7.0-0 <4.0.0"
flutter: ">=3.24.0"

View File

@ -5,10 +5,10 @@ packages:
dependency: transitive
description:
name: async
sha256: d2872f9c19731c2e5f10444b14686eb7cc85c76274bd6c16e1816bff9a3bab63
sha256: "758e6d74e971c3e5aceb4110bfd6698efc7f501675bcfe0c775459a8140750eb"
url: "https://pub.dev"
source: hosted
version: "2.12.0"
version: "2.13.0"
boolean_selector:
dependency: transitive
description:
@ -61,10 +61,10 @@ packages:
dependency: transitive
description:
name: fake_async
sha256: "6a95e56b2449df2273fd8c45a662d6947ce1ebb7aafe80e550a3f68297f3cacc"
sha256: "5368f224a74523e8d2e7399ea1638b37aecfca824a3cc4dfdf77bf1fa905ac44"
url: "https://pub.dev"
source: hosted
version: "1.3.2"
version: "1.3.3"
ffi:
dependency: transitive
description:
@ -135,10 +135,10 @@ packages:
dependency: transitive
description:
name: leak_tracker
sha256: c35baad643ba394b40aac41080300150a4f08fd0fd6a10378f8f7c6bc161acec
sha256: "6bb818ecbdffe216e81182c2f0714a2e62b593f4a4f13098713ff1685dfb6ab0"
url: "https://pub.dev"
source: hosted
version: "10.0.8"
version: "10.0.9"
leak_tracker_flutter_testing:
dependency: transitive
description:
@ -339,10 +339,10 @@ packages:
dependency: transitive
description:
name: vm_service
sha256: "0968250880a6c5fe7edc067ed0a13d4bae1577fe2771dcf3010d52c4a9d3ca14"
sha256: ddfa8d30d89985b96407efce8acbdd124701f96741f2d981ca860662f1c0dc02
url: "https://pub.dev"
source: hosted
version: "14.3.1"
version: "15.0.0"
web:
dependency: transitive
description:

View File

@ -514,18 +514,18 @@ packages:
dependency: "direct main"
description:
name: flex_color_scheme
sha256: ae638050fceb35b6040a43cf67892f9b956022068e736284919d93322fdd4ba2
sha256: "3344f8f6536c6ce0473b98e9f084ef80ca89024ad3b454f9c32cf840206f4387"
url: "https://pub.dev"
source: hosted
version: "8.1.1"
version: "8.2.0"
flex_seed_scheme:
dependency: transitive
description:
name: flex_seed_scheme
sha256: d3ba3c5c92d2d79d45e94b4c6c71d01fac3c15017da1545880c53864da5dfeb0
sha256: b06d8b367b84cbf7ca5c5603c858fa5edae88486c4e4da79ac1044d73b6c62ec
url: "https://pub.dev"
source: hosted
version: "3.5.0"
version: "3.5.1"
flutter:
dependency: "direct main"
description: flutter
@ -835,6 +835,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "0.6.7"
json5:
dependency: transitive
description:
name: json5
sha256: b67d6e06c9e225c8277d3c43f796677af7975a2a2b0669ff12ba38ff466a31f4
url: "https://pub.dev"
source: hosted
version: "0.8.2"
json_annotation:
dependency: "direct main"
description:

View File

@ -14,6 +14,7 @@ dependencies:
path: packages/apidash_core
apidash_design_system:
path: packages/apidash_design_system
carousel_slider: ^5.0.0
code_builder: ^4.10.0
csv: ^6.0.0
data_table_2: 2.5.16
@ -21,13 +22,18 @@ dependencies:
desktop_drop: ^0.5.0
extended_text_field: ^16.0.0
file_selector: ^1.0.3
flex_color_scheme: ^8.1.1
flex_color_scheme: ^8.2.0
flutter_code_editor: ^0.3.3
flutter_highlight: ^0.7.0
flutter_highlighter: ^0.1.0
flutter_hooks: ^0.21.2
flutter_js: ^0.8.2
flutter_markdown: ^0.7.6+2
flutter_portal: ^1.1.4
flutter_riverpod: ^2.5.1
flutter_svg: ^2.0.17
fvp: ^0.30.0
highlight: ^0.7.0
hive_flutter: ^1.1.0
hooks_riverpod: ^2.5.2
intl: ^0.19.0
@ -67,11 +73,6 @@ dependencies:
git:
url: https://github.com/google/flutter-desktop-embedding.git
path: plugins/window_size
carousel_slider: ^5.0.0
flutter_code_editor: ^0.3.3
highlight: ^0.7.0
flutter_highlight: ^0.7.0
flutter_js: ^0.8.2
dependency_overrides:
extended_text_field: ^16.0.0

View File

@ -18,6 +18,7 @@ void main() {
historyRetentionPeriod: HistoryRetentionPeriod.oneWeek,
workspaceFolderPath: null,
isSSLDisabled: true,
isDashBotEnabled: true,
);
test('Testing toJson()', () {
@ -36,6 +37,7 @@ void main() {
"historyRetentionPeriod": "oneWeek",
"workspaceFolderPath": null,
"isSSLDisabled": true,
"isDashBotEnabled": true,
};
expect(sm.toJson(), expectedResult);
});
@ -56,6 +58,7 @@ void main() {
"historyRetentionPeriod": "oneWeek",
"workspaceFolderPath": null,
"isSSLDisabled": true,
"isDashBotEnabled": true,
};
expect(SettingsModel.fromJson(input), sm);
});
@ -73,12 +76,14 @@ void main() {
activeEnvironmentId: null,
historyRetentionPeriod: HistoryRetentionPeriod.oneWeek,
isSSLDisabled: false,
isDashBotEnabled: false,
);
expect(
sm.copyWith(
isDark: true,
saveResponses: false,
isSSLDisabled: false,
isDashBotEnabled: false,
),
expectedResult);
});
@ -98,7 +103,8 @@ void main() {
"activeEnvironmentId": null,
"historyRetentionPeriod": "oneWeek",
"workspaceFolderPath": null,
"isSSLDisabled": true
"isSSLDisabled": true,
"isDashBotEnabled": true
}''';
expect(sm.toString(), expectedResult);
});