mirror of
https://github.com/foss42/apidash.git
synced 2025-07-25 11:57:25 +08:00
Merge branch 'foss42:main' into add-request-cancellation
This commit is contained in:
28
ROADMAP.md
28
ROADMAP.md
@ -1,15 +1,31 @@
|
||||
## API Dash Roadmap
|
||||
|
||||
### L1 Priority (PRs will be actively reviewed)
|
||||
|
||||
- [x] Remaining Code Generators (https://github.com/foss42/apidash/discussions/80)
|
||||
- [x] Environment Variables (https://github.com/foss42/apidash/issues/25)
|
||||
- [ ] Git Support (https://github.com/foss42/apidash/issues/502)
|
||||
- [x] Integration Testing (https://github.com/foss42/apidash/issues/119)
|
||||
- [ ] Paste not working on iOS and desktop (Right Click -> Paste) (https://github.com/foss42/apidash/issues/490)
|
||||
- [ ] WebSocket support (https://github.com/foss42/apidash/issues/15)
|
||||
- [ ] SSE support (https://github.com/foss42/apidash/issues/116)
|
||||
- [ ] MQTT support (https://github.com/foss42/apidash/issues/115)
|
||||
- [ ] GraphQL support (https://github.com/foss42/apidash/issues/117)
|
||||
- [ ] gRPC support (https://github.com/foss42/apidash/issues/14)
|
||||
- [ ] API Testing Suite (https://github.com/foss42/apidash/discussions/96, https://github.com/foss42/apidash/issues/100)
|
||||
- [ ] API Workflow Builder (https://github.com/foss42/apidash/issues/120)
|
||||
- [x] Integration Testing (https://github.com/foss42/apidash/issues/119)
|
||||
- [ ] Remaining Code Generators (https://github.com/foss42/apidash/discussions/80)
|
||||
- [ ] Figuring out how to build for various Linux packaging formats (https://github.com/foss42/apidash/discussions/240) [Docs]
|
||||
- [ ] Save items in Response headers/body in an Environment variable to be used by other requests (https://github.com/foss42/apidash/issues/465)
|
||||
- [ ] Importers
|
||||
- [ ] OpenAPI (https://github.com/foss42/apidash/issues/121)
|
||||
- [ ] Insomnia (https://github.com/foss42/apidash/issues/125)
|
||||
- [ ] Hurl (https://github.com/foss42/apidash/issues/123)
|
||||
- [ ] HAR (https://github.com/foss42/apidash/issues/122)
|
||||
|
||||
### L2 Priority (Will require more design/technical research & thinking and PRs will consume more time)
|
||||
|
||||
- [ ] Embedded WebView in Response Previewer (https://github.com/foss42/apidash/issues/155)
|
||||
- [ ] Figuring out how to build for various Linux packaging formats (https://github.com/foss42/apidash/discussions/240)
|
||||
- [ ] Git Support (https://github.com/foss42/apidash/issues/502)
|
||||
- [ ] API Testing Suite (https://github.com/foss42/apidash/discussions/96)
|
||||
- [ ] Provide Mock Data (https://github.com/foss42/apidash/issues/496)
|
||||
- [ ] Ability to stress test APIs (https://github.com/foss42/apidash/issues/100)
|
||||
- [ ] API Workflow Builder (https://github.com/foss42/apidash/issues/120)
|
||||
- [ ] OAuth 2.0 auth (https://github.com/foss42/apidash/issues/481)
|
||||
- [ ] Add UI scaling (https://github.com/foss42/apidash/issues/466)
|
||||
|
6
doc/dev_guide/README.md
Normal file
6
doc/dev_guide/README.md
Normal file
@ -0,0 +1,6 @@
|
||||
# API Dash Developer Guide
|
||||
|
||||
1. [How to run API Dash locally?](https://github.com/foss42/apidash/blob/main/doc/dev_guide/setup_run.md)
|
||||
2. [Platform-specific Additional Instructions](https://github.com/foss42/apidash/blob/main/doc/dev_guide/platform_specific_instructions.md)
|
||||
3. [How to run tests?](https://github.com/foss42/apidash/blob/main/doc/dev_guide/testing.md)
|
||||
4. [Integration Testing](https://github.com/foss42/apidash/blob/main/doc/dev_guide/integration_testing.md)
|
@ -433,6 +433,7 @@ const kLabelDuplicate = "Duplicate";
|
||||
const kLabelSelect = "Select";
|
||||
const kLabelContinue = "Continue";
|
||||
const kLabelCancel = "Cancel";
|
||||
const kLabelOk = "Ok";
|
||||
// Request Pane
|
||||
const kLabelRequest = "Request";
|
||||
const kLabelHideCode = "Hide Code";
|
||||
|
@ -8,31 +8,25 @@ class CurlFileImport {
|
||||
final curl = Curl.parse(content);
|
||||
final url = stripUriParams(curl.uri);
|
||||
final method = HTTPVerb.values.byName(curl.method.toLowerCase());
|
||||
|
||||
final headers = curl.headers?.entries
|
||||
.map((entry) => NameValueModel(
|
||||
name: entry.key,
|
||||
value: entry.value,
|
||||
))
|
||||
.toList();
|
||||
|
||||
final params = curl.uri.queryParameters.entries
|
||||
.map((entry) => NameValueModel(
|
||||
name: entry.key,
|
||||
value: entry.value,
|
||||
))
|
||||
.toList();
|
||||
|
||||
// TODO: parse curl data to determine the type of body
|
||||
final headers = mapToRows(curl.headers);
|
||||
final params = mapToRows(curl.uri.queryParameters);
|
||||
final body = curl.data;
|
||||
// TODO: formdata with file paths must be set to empty as
|
||||
// there will be permission issue while trying to access the path
|
||||
final formData = curl.formData;
|
||||
// Determine content type based on form data and headers
|
||||
final ContentType contentType = curl.form
|
||||
? ContentType.formdata
|
||||
: (getContentTypeFromHeadersMap(curl.headers) ?? ContentType.text);
|
||||
|
||||
return HttpRequestModel(
|
||||
method: method,
|
||||
url: url,
|
||||
headers: headers,
|
||||
params: params,
|
||||
body: body,
|
||||
);
|
||||
method: method,
|
||||
url: url,
|
||||
headers: headers,
|
||||
params: params,
|
||||
body: body,
|
||||
bodyContentType: contentType,
|
||||
formData: formData);
|
||||
} catch (e) {
|
||||
return null;
|
||||
}
|
||||
|
51
lib/importer/import_dialog.dart
Normal file
51
lib/importer/import_dialog.dart
Normal file
@ -0,0 +1,51 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:apidash/providers/providers.dart';
|
||||
import 'package:apidash/widgets/widgets.dart';
|
||||
import 'importer.dart';
|
||||
|
||||
void importToCollectionPane(
|
||||
BuildContext context,
|
||||
WidgetRef ref,
|
||||
ScaffoldMessengerState sm,
|
||||
) {
|
||||
// TODO: The dialog must have a feature to paste contents in a text field
|
||||
// Also, a mechanism can be added where on importing a file it shows the
|
||||
// contents in the text field and then the user presses ok to add it to collection
|
||||
showImportDialog(
|
||||
context: context,
|
||||
importFormat: ref.watch(importFormatStateProvider),
|
||||
onImportFormatChange: (format) {
|
||||
if (format != null) {
|
||||
ref.read(importFormatStateProvider.notifier).state = format;
|
||||
}
|
||||
},
|
||||
onFileDropped: (file) {
|
||||
final importFormatType = ref.read(importFormatStateProvider);
|
||||
sm.hideCurrentSnackBar();
|
||||
file.readAsString().then(
|
||||
(content) {
|
||||
kImporter
|
||||
.getHttpRequestModel(importFormatType, content)
|
||||
.then((importedRequestModel) {
|
||||
if (importedRequestModel != null) {
|
||||
ref
|
||||
.read(collectionStateNotifierProvider.notifier)
|
||||
.addRequestModel(importedRequestModel);
|
||||
// Solves - Do not use BuildContexts across async gaps
|
||||
if (!context.mounted) return;
|
||||
Navigator.of(context).pop();
|
||||
} else {
|
||||
var err = "Unable to parse ${file.name}";
|
||||
sm.showSnackBar(getSnackBar(err, small: false));
|
||||
}
|
||||
});
|
||||
},
|
||||
onError: (e) {
|
||||
var err = "Unable to import ${file.name}";
|
||||
sm.showSnackBar(getSnackBar(err, small: false));
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
@ -13,3 +13,5 @@ class Importer {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
final kImporter = Importer();
|
||||
|
@ -1,16 +1,14 @@
|
||||
import 'package:apidash_design_system/apidash_design_system.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:apidash/importer/import_dialog.dart';
|
||||
import 'package:apidash/providers/providers.dart';
|
||||
import 'package:apidash/importer/importer.dart';
|
||||
import 'package:apidash/extensions/extensions.dart';
|
||||
import 'package:apidash/widgets/widgets.dart';
|
||||
import 'package:apidash/models/models.dart';
|
||||
import 'package:apidash/consts.dart';
|
||||
import '../common_widgets/common_widgets.dart';
|
||||
|
||||
final kImporter = Importer();
|
||||
|
||||
class CollectionPane extends ConsumerWidget {
|
||||
const CollectionPane({
|
||||
super.key,
|
||||
@ -19,6 +17,7 @@ class CollectionPane extends ConsumerWidget {
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final collection = ref.watch(collectionStateNotifierProvider);
|
||||
var sm = ScaffoldMessenger.of(context);
|
||||
if (collection == null) {
|
||||
return const Center(
|
||||
child: CircularProgressIndicator(),
|
||||
@ -37,32 +36,7 @@ class CollectionPane extends ConsumerWidget {
|
||||
ref.read(collectionStateNotifierProvider.notifier).add();
|
||||
},
|
||||
onImport: () {
|
||||
showImportDialog(
|
||||
context: context,
|
||||
importFormat: ref.watch(importFormatStateProvider),
|
||||
onImportFormatChange: (format) {
|
||||
if (format != null) {
|
||||
ref.read(importFormatStateProvider.notifier).state = format;
|
||||
}
|
||||
},
|
||||
onFileDropped: (file) {
|
||||
final importFormatType = ref.read(importFormatStateProvider);
|
||||
file.readAsString().then((content) {
|
||||
kImporter
|
||||
.getHttpRequestModel(importFormatType, content)
|
||||
.then((importedRequestModel) {
|
||||
if (importedRequestModel != null) {
|
||||
ref
|
||||
.read(collectionStateNotifierProvider.notifier)
|
||||
.addRequestModel(importedRequestModel);
|
||||
} else {
|
||||
// TODO: Throw an error, unable to parse
|
||||
}
|
||||
});
|
||||
});
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
);
|
||||
importToCollectionPane(context, ref, sm);
|
||||
},
|
||||
),
|
||||
kVSpacer10,
|
||||
|
33
lib/widgets/dialog_text.dart
Normal file
33
lib/widgets/dialog_text.dart
Normal file
@ -0,0 +1,33 @@
|
||||
import 'package:apidash/consts.dart';
|
||||
import 'package:apidash_design_system/apidash_design_system.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
showTextDialog(
|
||||
BuildContext context, {
|
||||
String? dialogTitle,
|
||||
String? content,
|
||||
String? buttonLabel,
|
||||
}) {
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (context) {
|
||||
return AlertDialog(
|
||||
icon: const Icon(Icons.edit_rounded),
|
||||
iconColor: Theme.of(context).colorScheme.primary,
|
||||
title: Text(dialogTitle ?? ""),
|
||||
titleTextStyle: Theme.of(context).textTheme.titleLarge,
|
||||
content: Container(
|
||||
padding: kPt20,
|
||||
width: 300,
|
||||
child: Text(content ?? ""),
|
||||
),
|
||||
actions: <Widget>[
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
Navigator.pop(context);
|
||||
},
|
||||
child: Text(buttonLabel ?? kLabelOk)),
|
||||
],
|
||||
);
|
||||
});
|
||||
}
|
@ -18,6 +18,7 @@ export 'dialog_about.dart';
|
||||
export 'dialog_history_retention.dart';
|
||||
export 'dialog_import.dart';
|
||||
export 'dialog_rename.dart';
|
||||
export 'dialog_text.dart';
|
||||
export 'drag_and_drop_area.dart';
|
||||
export 'dropdown_codegen.dart';
|
||||
export 'dropdown_content_type.dart';
|
||||
|
@ -1 +1,2 @@
|
||||
export 'string_extensions.dart';
|
||||
export 'map_extensions.dart';
|
||||
|
26
packages/apidash_core/lib/extensions/map_extensions.dart
Normal file
26
packages/apidash_core/lib/extensions/map_extensions.dart
Normal file
@ -0,0 +1,26 @@
|
||||
import 'dart:io';
|
||||
|
||||
extension MapExtension on Map {
|
||||
bool hasKeyContentType() {
|
||||
return keys.any((k) => (k is String)
|
||||
? k.toLowerCase() == HttpHeaders.contentTypeHeader
|
||||
: false);
|
||||
}
|
||||
|
||||
String? getKeyContentType() {
|
||||
if (isEmpty) {
|
||||
return null;
|
||||
}
|
||||
bool present = hasKeyContentType();
|
||||
if (present) {
|
||||
return keys.firstWhere((e) => (e is String)
|
||||
? e.toLowerCase() == HttpHeaders.contentTypeHeader
|
||||
: false);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
String? getValueContentType() {
|
||||
return this[getKeyContentType()];
|
||||
}
|
||||
}
|
@ -1,7 +1,7 @@
|
||||
import 'dart:io';
|
||||
import 'dart:convert';
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
import 'package:seed/seed.dart';
|
||||
import '../extensions/extensions.dart';
|
||||
import '../utils/utils.dart'
|
||||
show rowsToFormDataMapList, rowsToMap, getEnabledRows;
|
||||
import '../consts.dart';
|
||||
@ -43,8 +43,7 @@ class HttpRequestModel with _$HttpRequestModel {
|
||||
Map<String, String> get enabledHeadersMap => rowsToMap(enabledHeaders) ?? {};
|
||||
Map<String, String> get enabledParamsMap => rowsToMap(enabledParams) ?? {};
|
||||
|
||||
bool get hasContentTypeHeader => enabledHeadersMap.keys
|
||||
.any((k) => k.toLowerCase() == HttpHeaders.contentTypeHeader);
|
||||
bool get hasContentTypeHeader => enabledHeadersMap.hasKeyContentType();
|
||||
bool get hasFormDataContentType => bodyContentType == ContentType.formdata;
|
||||
bool get hasJsonContentType => bodyContentType == ContentType.json;
|
||||
bool get hasTextContentType => bodyContentType == ContentType.text;
|
||||
|
@ -5,6 +5,7 @@ import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
import 'package:collection/collection.dart' show mergeMaps;
|
||||
import 'package:http/http.dart';
|
||||
import 'package:http_parser/http_parser.dart';
|
||||
import '../extensions/extensions.dart';
|
||||
import '../utils/utils.dart';
|
||||
import '../consts.dart';
|
||||
|
||||
@ -61,7 +62,7 @@ class HttpResponseModel with _$HttpResponseModel {
|
||||
factory HttpResponseModel.fromJson(Map<String, Object?> json) =>
|
||||
_$HttpResponseModelFromJson(json);
|
||||
|
||||
String? get contentType => getContentTypeFromHeaders(headers);
|
||||
String? get contentType => headers?.getValueContentType();
|
||||
MediaType? get mediaType => getMediaTypeFromHeaders(headers);
|
||||
|
||||
HttpResponseModel fromResponse({
|
||||
|
39
packages/apidash_core/lib/utils/content_type_utils.dart
Normal file
39
packages/apidash_core/lib/utils/content_type_utils.dart
Normal file
@ -0,0 +1,39 @@
|
||||
import 'package:http_parser/http_parser.dart';
|
||||
import '../consts.dart';
|
||||
import '../extensions/extensions.dart';
|
||||
|
||||
ContentType? getContentTypeFromHeadersMap(
|
||||
Map<String, String>? kvMap,
|
||||
) {
|
||||
if (kvMap != null && kvMap.hasKeyContentType()) {
|
||||
var val = getMediaTypeFromHeaders(kvMap);
|
||||
if (val != null) {
|
||||
if (val.subtype.contains(kSubTypeJson)) {
|
||||
return ContentType.json;
|
||||
} else if (val.type == kTypeMultipart &&
|
||||
val.subtype == kSubTypeFormData) {
|
||||
return ContentType.formdata;
|
||||
}
|
||||
return ContentType.text;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
MediaType? getMediaTypeFromHeaders(Map? headers) {
|
||||
var contentType = headers?.getValueContentType();
|
||||
MediaType? mediaType = getMediaTypeFromContentType(contentType);
|
||||
return mediaType;
|
||||
}
|
||||
|
||||
MediaType? getMediaTypeFromContentType(String? contentType) {
|
||||
if (contentType != null) {
|
||||
try {
|
||||
MediaType mediaType = MediaType.parse(contentType);
|
||||
return mediaType;
|
||||
} catch (e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
@ -1,8 +1,10 @@
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:seed/seed.dart';
|
||||
|
||||
Map<String, String>? rowsToMap(List<NameValueModel>? kvRows,
|
||||
{bool isHeader = false}) {
|
||||
Map<String, String>? rowsToMap(
|
||||
List<NameValueModel>? kvRows, {
|
||||
bool isHeader = false,
|
||||
}) {
|
||||
if (kvRows == null) {
|
||||
return null;
|
||||
}
|
||||
@ -19,7 +21,9 @@ Map<String, String>? rowsToMap(List<NameValueModel>? kvRows,
|
||||
return finalMap;
|
||||
}
|
||||
|
||||
List<NameValueModel>? mapToRows(Map<String, String>? kvMap) {
|
||||
List<NameValueModel>? mapToRows(
|
||||
Map<String, String>? kvMap,
|
||||
) {
|
||||
if (kvMap == null) {
|
||||
return null;
|
||||
}
|
||||
@ -50,7 +54,9 @@ List<Map<String, String>>? rowsToFormDataMapList(
|
||||
return finalMap;
|
||||
}
|
||||
|
||||
List<FormDataModel>? mapListToFormDataModelRows(List<Map>? kvMap) {
|
||||
List<FormDataModel>? mapListToFormDataModelRows(
|
||||
List<Map>? kvMap,
|
||||
) {
|
||||
if (kvMap == null) {
|
||||
return null;
|
||||
}
|
||||
@ -72,7 +78,9 @@ FormDataType getFormDataType(String? type) {
|
||||
}
|
||||
|
||||
List<NameValueModel>? getEnabledRows(
|
||||
List<NameValueModel>? rows, List<bool>? isRowEnabledList) {
|
||||
List<NameValueModel>? rows,
|
||||
List<bool>? isRowEnabledList,
|
||||
) {
|
||||
if (rows == null || isRowEnabledList == null) {
|
||||
return rows;
|
||||
}
|
||||
|
@ -1,33 +1,10 @@
|
||||
import 'dart:convert';
|
||||
import 'dart:io';
|
||||
import 'dart:typed_data';
|
||||
import 'package:http/http.dart' as http;
|
||||
import 'package:http_parser/http_parser.dart';
|
||||
import 'package:xml/xml.dart';
|
||||
import '../consts.dart';
|
||||
|
||||
String? getContentTypeFromHeaders(Map? headers) {
|
||||
return headers?[HttpHeaders.contentTypeHeader];
|
||||
}
|
||||
|
||||
MediaType? getMediaTypeFromHeaders(Map? headers) {
|
||||
var contentType = getContentTypeFromHeaders(headers);
|
||||
MediaType? mediaType = getMediaTypeFromContentType(contentType);
|
||||
return mediaType;
|
||||
}
|
||||
|
||||
MediaType? getMediaTypeFromContentType(String? contentType) {
|
||||
if (contentType != null) {
|
||||
try {
|
||||
MediaType mediaType = MediaType.parse(contentType);
|
||||
return mediaType;
|
||||
} catch (e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
String? formatBody(String? body, MediaType? mediaType) {
|
||||
if (mediaType != null && body != null) {
|
||||
var subtype = mediaType.subtype;
|
||||
|
@ -1,3 +1,4 @@
|
||||
export 'content_type_utils.dart';
|
||||
export 'http_request_utils.dart';
|
||||
export 'http_response_utils.dart';
|
||||
export 'string_utils.dart';
|
||||
|
129
packages/apidash_core/test/extensions/map_extensions_test.dart
Normal file
129
packages/apidash_core/test/extensions/map_extensions_test.dart
Normal file
@ -0,0 +1,129 @@
|
||||
import 'package:apidash_core/apidash_core.dart';
|
||||
import 'package:test/test.dart';
|
||||
|
||||
void main() {
|
||||
group('Testing MapExtensions', () {
|
||||
group('Testing hasKeyContentType()', () {
|
||||
test('Content-Type present should return true', () {
|
||||
Map<String, String> mapEx = {"Content-Type": "x", "Agent": "Test"};
|
||||
expect(mapEx.hasKeyContentType(), true);
|
||||
});
|
||||
|
||||
test('content-Type present should return true', () {
|
||||
Map<String, String> mapEx = {"content-Type": "x", "Agent": "Test"};
|
||||
expect(mapEx.hasKeyContentType(), true);
|
||||
});
|
||||
|
||||
test('empty should return false', () {
|
||||
Map<String, String> mapEx = {};
|
||||
expect(mapEx.hasKeyContentType(), false);
|
||||
});
|
||||
|
||||
test('No content-type present should return false', () {
|
||||
Map<String, String> mapEx = {"Agent": "Test"};
|
||||
expect(mapEx.hasKeyContentType(), false);
|
||||
});
|
||||
|
||||
test('Different datatype should return false', () {
|
||||
Map mapEx = {1: "Test"};
|
||||
expect(mapEx.hasKeyContentType(), false);
|
||||
});
|
||||
|
||||
test('Mixed datatype but should return true', () {
|
||||
Map mapEx = {1: "Test", "content-type": "x"};
|
||||
expect(mapEx.hasKeyContentType(), true);
|
||||
});
|
||||
});
|
||||
|
||||
group('Testing getKeyContentType()', () {
|
||||
test('Content-Type present', () {
|
||||
Map<String, String> mapEx = {"Agent": "Test", "Content-Type": "x"};
|
||||
expect(mapEx.getKeyContentType(), "Content-Type");
|
||||
});
|
||||
|
||||
test('content-Type present', () {
|
||||
Map<String, String> mapEx = {"Agent": "Test", "content-Type": "x"};
|
||||
expect(mapEx.getKeyContentType(), "content-Type");
|
||||
});
|
||||
|
||||
test('empty should return null', () {
|
||||
Map<String, String> mapEx = {};
|
||||
expect(mapEx.getKeyContentType(), null);
|
||||
});
|
||||
|
||||
test('No content-type present should return null', () {
|
||||
Map<String, String> mapEx = {"Agent": "Test"};
|
||||
expect(mapEx.getKeyContentType(), null);
|
||||
});
|
||||
|
||||
test('Different datatype should return null', () {
|
||||
Map mapEx = {1: "Test"};
|
||||
expect(mapEx.getKeyContentType(), null);
|
||||
});
|
||||
|
||||
test('Mixed datatype but should return content-type', () {
|
||||
Map mapEx = {1: "Test", "content-type": "x"};
|
||||
expect(mapEx.getKeyContentType(), "content-type");
|
||||
});
|
||||
|
||||
test('Multiple occurence should return first', () {
|
||||
Map mapEx = {1: "Test", "content-Type": "y", "content-type": "x"};
|
||||
expect(mapEx.getKeyContentType(), "content-Type");
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
group('Testing getValueContentType()', () {
|
||||
test('Content-Type present', () {
|
||||
Map<String, String> mapEx = {"Agent": "Test", "Content-Type": "x"};
|
||||
expect(mapEx.getValueContentType(), "x");
|
||||
});
|
||||
|
||||
test('content-Type present', () {
|
||||
Map<String, String> mapEx = {"Agent": "Test", "content-Type": "x"};
|
||||
expect(mapEx.getValueContentType(), "x");
|
||||
});
|
||||
|
||||
test('empty should return null', () {
|
||||
Map<String, String> mapEx = {};
|
||||
expect(mapEx.getValueContentType(), null);
|
||||
});
|
||||
|
||||
test('No content-type present should return null', () {
|
||||
Map<String, String> mapEx = {"Agent": "Test"};
|
||||
expect(mapEx.getValueContentType(), null);
|
||||
});
|
||||
|
||||
test('Different datatype should return null', () {
|
||||
Map mapEx = {1: "Test"};
|
||||
expect(mapEx.getValueContentType(), null);
|
||||
});
|
||||
|
||||
test('Mixed datatype but should return x', () {
|
||||
Map mapEx = {1: "Test", "content-type": "x"};
|
||||
expect(mapEx.getValueContentType(), "x");
|
||||
});
|
||||
|
||||
test('Multiple occurence should return first', () {
|
||||
Map mapEx = {1: "Test", "content-Type": "y", "content-type": "x"};
|
||||
expect(mapEx.getValueContentType(), "y");
|
||||
});
|
||||
});
|
||||
|
||||
group("Testing ?.getValueContentType() function", () {
|
||||
test('Testing ?.getValueContentType() for header1', () {
|
||||
Map<String, String> header1 = {
|
||||
"content-type": "application/json",
|
||||
};
|
||||
String contentType1Expected = "application/json";
|
||||
expect(header1.getValueContentType(), contentType1Expected);
|
||||
});
|
||||
test('Testing ?.getValueContentType() when header keys are in header case',
|
||||
() {
|
||||
Map<String, String> header2 = {
|
||||
"Content-Type": "application/json",
|
||||
};
|
||||
expect(header2.getValueContentType(), "application/json");
|
||||
});
|
||||
});
|
||||
}
|
@ -1,30 +1,8 @@
|
||||
import 'package:apidash_core/utils/http_response_utils.dart';
|
||||
import 'package:apidash_core/utils/string_utils.dart';
|
||||
import 'package:apidash_core/utils/utils.dart';
|
||||
import 'package:http_parser/http_parser.dart';
|
||||
import 'package:test/test.dart';
|
||||
|
||||
void main() {
|
||||
group("Testing getContentTypeFromHeaders function", () {
|
||||
test('Testing getContentTypeFromHeaders for header1', () {
|
||||
Map<String, String> header1 = {
|
||||
"content-type": "application/json",
|
||||
};
|
||||
String contentType1Expected = "application/json";
|
||||
expect(getContentTypeFromHeaders(header1), contentType1Expected);
|
||||
});
|
||||
test('Testing getContentTypeFromHeaders for null headers', () {
|
||||
expect(getContentTypeFromHeaders(null), null);
|
||||
});
|
||||
test(
|
||||
'Testing getContentTypeFromHeaders when header keys are in header case',
|
||||
() {
|
||||
Map<String, String> header2 = {
|
||||
"Content-Type": "application/json",
|
||||
};
|
||||
expect(getContentTypeFromHeaders(header2), null);
|
||||
});
|
||||
});
|
||||
|
||||
group('Testing getMediaTypeFromContentType function', () {
|
||||
test('Testing getMediaTypeFromContentType for json type', () {
|
||||
String contentType1 = "application/json";
|
||||
|
34
packages/postman/.gitignore
vendored
34
packages/postman/.gitignore
vendored
@ -1,7 +1,31 @@
|
||||
# https://dart.dev/guides/libraries/private-files
|
||||
# Created by `dart pub`
|
||||
.dart_tool/
|
||||
# Miscellaneous
|
||||
*.class
|
||||
*.log
|
||||
*.pyc
|
||||
*.swp
|
||||
.DS_Store
|
||||
.atom/
|
||||
.buildlog/
|
||||
.history
|
||||
.svn/
|
||||
|
||||
# Avoid committing pubspec.lock for library packages; see
|
||||
# https://dart.dev/guides/libraries/private-files#pubspeclock.
|
||||
# IntelliJ related
|
||||
*.iml
|
||||
*.ipr
|
||||
*.iws
|
||||
.idea/
|
||||
|
||||
# The .vscode folder contains launch configuration and tasks you configure in
|
||||
# VS Code which you may wish to be included in version control, so this line
|
||||
# is commented out by default.
|
||||
#.vscode/
|
||||
|
||||
# Flutter/Dart/Pub related
|
||||
# Libraries should not include pubspec.lock, per https://dart.dev/guides/libraries/private-files#pubspeclock.
|
||||
pubspec.lock
|
||||
**/doc/api/
|
||||
.dart_tool/
|
||||
.packages
|
||||
build/
|
||||
**/golden/**/failures/
|
||||
coverage/
|
||||
|
31
packages/seed/.gitignore
vendored
31
packages/seed/.gitignore
vendored
@ -1,9 +1,30 @@
|
||||
# https://dart.dev/guides/libraries/private-files
|
||||
# Created by `dart pub`
|
||||
.dart_tool/
|
||||
# Miscellaneous
|
||||
*.class
|
||||
*.log
|
||||
*.pyc
|
||||
*.swp
|
||||
.DS_Store
|
||||
.atom/
|
||||
.buildlog/
|
||||
.history
|
||||
.svn/
|
||||
|
||||
# Avoid committing pubspec.lock for library packages; see
|
||||
# https://dart.dev/guides/libraries/private-files#pubspeclock.
|
||||
# IntelliJ related
|
||||
*.iml
|
||||
*.ipr
|
||||
*.iws
|
||||
.idea/
|
||||
|
||||
# The .vscode folder contains launch configuration and tasks you configure in
|
||||
# VS Code which you may wish to be included in version control, so this line
|
||||
# is commented out by default.
|
||||
#.vscode/
|
||||
|
||||
# Flutter/Dart/Pub related
|
||||
# Libraries should not include pubspec.lock, per https://dart.dev/guides/libraries/private-files#pubspeclock.
|
||||
pubspec.lock
|
||||
**/doc/api/
|
||||
.dart_tool/
|
||||
.packages
|
||||
build/
|
||||
coverage/
|
||||
|
94
test/importer/curl_test.dart
Normal file
94
test/importer/curl_test.dart
Normal file
@ -0,0 +1,94 @@
|
||||
import 'package:apidash/importer/curl/curl.dart';
|
||||
import 'package:test/test.dart';
|
||||
import 'package:apidash_core/apidash_core.dart';
|
||||
|
||||
void main() {
|
||||
group('CurlFileImport Tests', () {
|
||||
late CurlFileImport curlImport;
|
||||
|
||||
setUp(() {
|
||||
curlImport = CurlFileImport();
|
||||
});
|
||||
|
||||
test('should parse simple GET request', () {
|
||||
const curl = 'curl https://api.apidash.dev/users';
|
||||
final result = curlImport.getHttpRequestModel(curl);
|
||||
|
||||
expect(
|
||||
result,
|
||||
const HttpRequestModel(
|
||||
method: HTTPVerb.get,
|
||||
url: 'https://api.apidash.dev/users',
|
||||
headers: null,
|
||||
params: [],
|
||||
body: null,
|
||||
bodyContentType: ContentType.text,
|
||||
formData: null));
|
||||
});
|
||||
|
||||
test('should parse POST request with JSON body and headers', () {
|
||||
const curl = '''
|
||||
curl -X POST https://api.apidash.dev/users
|
||||
-H "Content-Type: application/json"
|
||||
-H "Authorization: Bearer token123"
|
||||
-d '{"name": "John", "age": 30}'
|
||||
''';
|
||||
|
||||
final result = curlImport.getHttpRequestModel(curl);
|
||||
|
||||
expect(
|
||||
result,
|
||||
const HttpRequestModel(
|
||||
method: HTTPVerb.post,
|
||||
url: 'https://api.apidash.dev/users',
|
||||
headers: [
|
||||
NameValueModel(name: 'Content-Type', value: 'application/json'),
|
||||
NameValueModel(name: 'Authorization', value: 'Bearer token123'),
|
||||
],
|
||||
params: [],
|
||||
body: '{"name": "John", "age": 30}',
|
||||
bodyContentType: ContentType.json,
|
||||
formData: null,
|
||||
),
|
||||
);
|
||||
});
|
||||
|
||||
test('should parse form data request', () {
|
||||
const curl = '''
|
||||
curl -X POST https://api.apidash.dev/upload
|
||||
-F "file=@photo.jpg"
|
||||
-F "description=My Photo"
|
||||
''';
|
||||
|
||||
final result = curlImport.getHttpRequestModel(curl);
|
||||
|
||||
expect(
|
||||
result,
|
||||
const HttpRequestModel(
|
||||
method: HTTPVerb.post,
|
||||
url: 'https://api.apidash.dev/upload',
|
||||
headers: [
|
||||
NameValueModel(name: "Content-Type", value: "multipart/form-data")
|
||||
],
|
||||
params: [],
|
||||
body: null,
|
||||
bodyContentType: ContentType.formdata,
|
||||
formData: [
|
||||
FormDataModel(
|
||||
name: 'file', value: 'photo.jpg', type: FormDataType.file),
|
||||
FormDataModel(
|
||||
name: 'description',
|
||||
value: 'My Photo',
|
||||
type: FormDataType.text),
|
||||
],
|
||||
));
|
||||
});
|
||||
|
||||
test('should return null for invalid curl command', () {
|
||||
const curl = 'invalid curl command';
|
||||
final result = curlImport.getHttpRequestModel(curl);
|
||||
|
||||
expect(result, isNull);
|
||||
});
|
||||
});
|
||||
}
|
Reference in New Issue
Block a user