code_work_spawner/test/support/fake_gh.dart

559 lines
16 KiB
Dart

import 'dart:convert';
import 'dart:io';
import 'script_utils.dart';
Future<void> writeFakeGhScript({
required File ghScript,
required List<Map<String, Object?>> issueListResponse,
required Map<int, List<Map<String, Object?>>> commentResponses,
Map<String, List<Map<String, Object?>>>? issueListResponsesByRepo,
Map<String, Map<int, List<Map<String, Object?>>>>? commentResponsesByRepo,
File? ghLog,
int? postCommentIssueNumber,
Set<int>? postCommentIssueNumbers,
Map<String, Set<int>>? postCommentIssueNumbersByRepo,
File? postedBodyFile,
File? reactionBodyFile,
String? viewerLogin,
bool failViewerLookup = false,
}) async {
final normalizedIssueListResponsesByRepo =
issueListResponsesByRepo ??
<String, List<Map<String, Object?>>>{'owner/sample': issueListResponse};
final normalizedCommentResponsesByRepo =
commentResponsesByRepo ??
<String, Map<int, List<Map<String, Object?>>>>{
'owner/sample': commentResponses,
};
final normalizedPostCommentIssueNumbersByRepo =
postCommentIssueNumbersByRepo ??
<String, Set<int>>{
'owner/sample': {
...?postCommentIssueNumbers,
...?postCommentIssueNumber == null ? null : {postCommentIssueNumber},
},
};
final searchItems =
normalizedIssueListResponsesByRepo.entries
.expand(
(entry) => entry.value.map(
(issue) => <String, Object?>{
...issue,
'repository_url': 'https://api.github.com/repos/${entry.key}',
},
),
)
.toList(growable: false)
..sort((left, right) {
final leftUpdatedAt = left['updated_at'] as String? ?? '';
final rightUpdatedAt = right['updated_at'] as String? ?? '';
return leftUpdatedAt.compareTo(rightUpdatedAt);
});
final openSearchItems = searchItems
.where((item) => item['state'] == 'open')
.toList(growable: false);
final allSearchResponse = jsonEncode({
'total_count': searchItems.length,
'incomplete_results': false,
'items': searchItems,
});
final openSearchResponse = jsonEncode({
'total_count': openSearchItems.length,
'incomplete_results': false,
'items': openSearchItems,
});
final buffer = StringBuffer()
..writeln('#!/usr/bin/env bash')
..writeln('set -euo pipefail');
if (ghLog != null) {
buffer.writeln(
r'''printf '%s\n' "$*" >> '''
'"${bashScriptPath(ghLog.path)}"',
);
}
buffer.writeln(r'''if [[ "$1" == "api" && "$2" == "user" ]]; then''');
if (failViewerLookup) {
buffer
..writeln(r''' echo "viewer lookup failed" >&2''')
..writeln(' exit 1')
..writeln('fi');
} else {
buffer
..writeln(" cat <<'JSON'")
..writeln(jsonEncode({'login': viewerLogin ?? 'test-user'}))
..writeln('JSON')
..writeln(' exit 0')
..writeln('fi');
}
buffer
..writeln(r'''if [[ "$1" == "api" && "$4" == "search/issues" ]]; then''')
..writeln(r''' query=""''')
..writeln(r''' for arg in "$@"; do''')
..writeln(r''' if [[ "$arg" == q=* ]]; then''')
..writeln(r''' query="${arg#q=}"''')
..writeln(r''' fi''')
..writeln(r''' done''')
..writeln(r''' if [[ "$query" == *"state:open"* ]]; then''')
..writeln(" cat <<'JSON'")
..writeln(openSearchResponse)
..writeln('JSON')
..writeln(r''' else''')
..writeln(" cat <<'JSON'")
..writeln(allSearchResponse)
..writeln('JSON')
..writeln(r''' fi''')
..writeln(' exit 0')
..writeln('fi');
for (final entry in normalizedIssueListResponsesByRepo.entries) {
final repo = entry.key;
buffer
..writeln(
'if [[ "\$1" == "api" && "\$4" == repos/$repo/issues\\?* ]]; then',
)
..writeln(" cat <<'JSON'")
..writeln(jsonEncode(entry.value))
..writeln('JSON')
..writeln(' exit 0')
..writeln('fi');
}
for (final repoEntry in normalizedCommentResponsesByRepo.entries) {
final repo = repoEntry.key;
for (final entry in repoEntry.value.entries) {
buffer
..writeln(
'if [[ "\$1" == "api" && "\$4" == '
'"repos/$repo/issues/${entry.key}/comments?per_page=100" ]]; then',
)
..writeln(" cat <<'JSON'")
..writeln(jsonEncode(entry.value))
..writeln('JSON')
..writeln(' exit 0')
..writeln('fi');
}
}
for (final repoEntry in normalizedPostCommentIssueNumbersByRepo.entries) {
final repo = repoEntry.key;
for (final issueNumber in repoEntry.value) {
buffer
..writeln(
'if [[ "\$1" == "api" && "\$4" == '
'"repos/$repo/issues/$issueNumber/comments" ]]; then',
)
..writeln(r''' for arg in "$@"; do''')
..writeln(r''' :''');
if (postedBodyFile != null) {
buffer
..writeln(r''' if [[ "$arg" == body=* ]]; then''')
..writeln(
r''' printf '%s' "${arg#body=}" > '''
'"${bashScriptPath(postedBodyFile.path)}"',
)
..writeln(r''' fi''');
}
buffer
..writeln(r''' done''')
..writeln(" cat <<'JSON'")
..writeln(
jsonEncode({
'id': 700,
'html_url': 'https://example.test/comment/700',
'body': 'posted',
}),
)
..writeln('JSON')
..writeln(' exit 0')
..writeln('fi');
}
}
for (final repoEntry in normalizedCommentResponsesByRepo.entries) {
final repo = repoEntry.key;
for (final issueEntry in repoEntry.value.entries) {
for (final comment in issueEntry.value) {
final commentId = comment['id'];
if (commentId is! int) {
continue;
}
buffer
..writeln(
'if [[ "\$1" == "api" && "\$4" == '
'"repos/$repo/issues/comments/$commentId/reactions" ]]; then',
)
..writeln(r''' for arg in "$@"; do''')
..writeln(r''' :''');
if (reactionBodyFile != null) {
buffer
..writeln(r''' if [[ "$arg" == content=* ]]; then''')
..writeln(
r''' printf '%s' "${arg#content=}" > '''
'"${bashScriptPath(reactionBodyFile.path)}"',
)
..writeln(r''' fi''');
}
buffer
..writeln(r''' done''')
..writeln(" cat <<'JSON'")
..writeln(jsonEncode({'id': 990, 'content': 'eyes'}))
..writeln('JSON')
..writeln(' exit 0')
..writeln('fi');
}
}
}
buffer
..writeln(r'''echo "unexpected gh args: $*" >&2''')
..writeln('exit 1');
await ghScript.writeAsString(buffer.toString());
await makeScriptExecutable(ghScript);
}
Future<void> writeFlakySearchGhScript({
required File ghScript,
required File failureStateFile,
required List<Map<String, Object?>> issueListResponse,
required Map<int, List<Map<String, Object?>>> commentResponses,
File? ghLog,
}) async {
final searchItems = issueListResponse
.map(
(issue) => <String, Object?>{
...issue,
'repository_url': 'https://api.github.com/repos/owner/sample',
},
)
.toList(growable: false);
final searchResponse = jsonEncode({
'total_count': searchItems.length,
'incomplete_results': false,
'items': searchItems,
});
final buffer = StringBuffer()
..writeln('#!/usr/bin/env bash')
..writeln('set -euo pipefail');
if (ghLog != null) {
buffer.writeln(
r'''printf '%s\n' "$*" >> '''
'"${bashScriptPath(ghLog.path)}"',
);
}
buffer
..writeln(r'''if [[ "$1" == "api" && "$2" == "user" ]]; then''')
..writeln(" cat <<'JSON'")
..writeln(jsonEncode({'login': 'test-user'}))
..writeln('JSON')
..writeln(' exit 0')
..writeln('fi');
buffer
..writeln(r'''if [[ "$1" == "api" && "$4" == "search/issues" ]]; then''')
..writeln(
' if [[ ! -f "${bashScriptPath(failureStateFile.path)}" ]]; then',
)
..writeln(' touch "${bashScriptPath(failureStateFile.path)}"')
..writeln(r''' echo "error connecting to api.github.com" >&2''')
..writeln(
r''' echo "check your internet connection or https://githubstatus.com" >&2''',
)
..writeln(' exit 1')
..writeln(r''' fi''')
..writeln(" cat <<'JSON'")
..writeln(searchResponse)
..writeln('JSON')
..writeln(' exit 0')
..writeln('fi');
for (final entry in commentResponses.entries) {
buffer
..writeln(
'if [[ "\$1" == "api" && "\$4" == '
'"repos/owner/sample/issues/${entry.key}/comments?per_page=100" ]]; then',
)
..writeln(" cat <<'JSON'")
..writeln(jsonEncode(entry.value))
..writeln('JSON')
..writeln(' exit 0')
..writeln('fi');
}
buffer
..writeln(r'''echo "unexpected gh args: $*" >&2''')
..writeln('exit 1');
await ghScript.writeAsString(buffer.toString());
await makeScriptExecutable(ghScript);
}
Future<void> writeWebhookForwardGhScript({
required File ghScript,
required String repo,
required Map<String, Object?> issue,
required List<Map<String, Object?>> comments,
List<Map<String, Object?>> issueListResponse = const [],
File? ghLog,
File? postedBodyFile,
String? viewerLogin,
}) async {
final issueNumber = issue['number'] as int;
final payload = {
'action': 'created',
'issue': issue,
'comment': comments.last,
'repository': {'full_name': repo},
};
final searchItems = issueListResponse
.map(
(searchIssue) => <String, Object?>{
...searchIssue,
'repository_url': 'https://api.github.com/repos/$repo',
},
)
.toList(growable: false);
final searchResponse = jsonEncode({
'total_count': searchItems.length,
'incomplete_results': false,
'items': searchItems,
});
final buffer = StringBuffer()
..writeln('#!/usr/bin/env bash')
..writeln('set -euo pipefail');
if (ghLog != null) {
buffer.writeln(
r'''printf '%s\n' "$*" >> '''
'"${bashScriptPath(ghLog.path)}"',
);
}
buffer.writeln(r'''if [[ "$1" == "api" && "$2" == "user" ]]; then''');
buffer
..writeln(" cat <<'JSON'")
..writeln(jsonEncode({'login': viewerLogin ?? 'test-user'}))
..writeln('JSON')
..writeln(' exit 0')
..writeln('fi');
buffer
..writeln(r'''if [[ "$1" == "api" && "$4" == "search/issues" ]]; then''')
..writeln(" cat <<'JSON'")
..writeln(searchResponse)
..writeln('JSON')
..writeln(' exit 0')
..writeln('fi');
buffer
..writeln(
'if [[ "\$1" == "api" && "\$4" == '
'"repos/$repo/issues/$issueNumber/comments?per_page=100" ]]; then',
)
..writeln(" cat <<'JSON'")
..writeln(jsonEncode(comments))
..writeln('JSON')
..writeln(' exit 0')
..writeln('fi');
buffer
..writeln(
'if [[ "\$1" == "api" && "\$4" == '
'"repos/$repo/issues/$issueNumber/comments" ]]; then',
)
..writeln(r''' for arg in "$@"; do''')
..writeln(r''' :''');
if (postedBodyFile != null) {
buffer
..writeln(r''' if [[ "$arg" == body=* ]]; then''')
..writeln(
r''' printf '%s' "${arg#body=}" > '''
'"${bashScriptPath(postedBodyFile.path)}"',
)
..writeln(r''' fi''');
}
buffer
..writeln(r''' done''')
..writeln(" cat <<'JSON'")
..writeln(
jsonEncode({
'id': 702,
'html_url': 'https://example.test/comment/702',
'body': 'posted',
}),
)
..writeln('JSON')
..writeln(' exit 0')
..writeln('fi');
buffer
..writeln(r'''if [[ "$1" == "webhook" && "$2" == "forward" ]]; then''')
..writeln(r''' url=""''')
..writeln(r''' for arg in "$@"; do''')
..writeln(r''' if [[ "$arg" == --url=* ]]; then''')
..writeln(r''' url="${arg#--url=}"''')
..writeln(r''' fi''')
..writeln(r''' done''')
..writeln(r''' if [[ -z "$url" ]]; then''')
..writeln(r''' echo "missing --url" >&2''')
..writeln(' exit 1')
..writeln(r''' fi''')
..writeln(r''' curl -sS -X POST \''')
..writeln(r''' -H "Content-Type: application/json" \''')
..writeln(r''' -H "X-GitHub-Event: issue_comment" \''')
..writeln(r''' --data-binary @- "$url" <<'JSON' >/dev/null''')
..writeln(jsonEncode(payload))
..writeln('JSON')
..writeln(r''' while true; do''')
..writeln(r''' sleep 60''')
..writeln(r''' done''')
..writeln('fi');
buffer
..writeln(r'''echo "unexpected gh args: $*" >&2''')
..writeln('exit 1');
await ghScript.writeAsString(buffer.toString());
await makeScriptExecutable(ghScript);
}
Future<void> writeGitHubGosmeeGhScript({
required File ghScript,
required String repo,
required int webhookId,
required int issueNumber,
required List<Map<String, Object?>> comments,
List<Map<String, Object?>> issueListResponse = const [],
File? ghLog,
File? postedBodyFile,
String? viewerLogin,
}) async {
final searchItems = issueListResponse
.map(
(issue) => <String, Object?>{
...issue,
'repository_url': 'https://api.github.com/repos/$repo',
},
)
.toList(growable: false);
final searchResponse = jsonEncode({
'total_count': searchItems.length,
'incomplete_results': false,
'items': searchItems,
});
final buffer = StringBuffer()
..writeln('#!/usr/bin/env bash')
..writeln('set -euo pipefail');
if (ghLog != null) {
buffer.writeln(
r'''printf '%s\n' "$*" >> '''
'"${bashScriptPath(ghLog.path)}"',
);
}
buffer.writeln(r'''if [[ "$1" == "api" && "$2" == "user" ]]; then''');
buffer
..writeln(" cat <<'JSON'")
..writeln(jsonEncode({'login': viewerLogin ?? 'test-user'}))
..writeln('JSON')
..writeln(' exit 0')
..writeln('fi');
buffer
..writeln(r'''if [[ "$1" == "api" && "$4" == "search/issues" ]]; then''')
..writeln(" cat <<'JSON'")
..writeln(searchResponse)
..writeln('JSON')
..writeln(' exit 0')
..writeln('fi');
buffer
..writeln(
'if [[ "\$1" == "api" && "\$4" == '
'"repos/$repo/issues/$issueNumber/comments?per_page=100" ]]; then',
)
..writeln(" cat <<'JSON'")
..writeln(jsonEncode(comments))
..writeln('JSON')
..writeln(' exit 0')
..writeln('fi');
buffer
..writeln('if [[ "\$1" == "api" && "\$4" == "repos/$repo/hooks" ]]; then')
..writeln(r''' active_flag=""''')
..writeln(r''' for ((i = 1; i <= $#; i += 1)); do''')
..writeln(r''' if [[ "${!i}" == "active=true" ]]; then''')
..writeln(r''' prev_index=$((i - 1))''')
..writeln(r''' active_flag="${!prev_index}"''')
..writeln(r''' break''')
..writeln(r''' fi''')
..writeln(r''' done''')
..writeln(
r''' if [[ "$active_flag" != "-F" && "$active_flag" != "--field" ]]; then''',
)
..writeln(
r''' echo "expected active=true to be sent with -F/--field, got: $*" >&2''',
)
..writeln(r''' exit 1''')
..writeln(r''' fi''')
..writeln(" cat <<'JSON'")
..writeln(jsonEncode({'id': webhookId}))
..writeln('JSON')
..writeln(' exit 0')
..writeln('fi');
buffer
..writeln(
'if [[ "\$1" == "api" && "\$4" == '
'"repos/$repo/issues/$issueNumber/comments" ]]; then',
)
..writeln(r''' for arg in "$@"; do''')
..writeln(r''' :''');
if (postedBodyFile != null) {
buffer
..writeln(r''' if [[ "$arg" == body=* ]]; then''')
..writeln(
r''' printf '%s' "${arg#body=}" > '''
'"${bashScriptPath(postedBodyFile.path)}"',
)
..writeln(r''' fi''');
}
buffer
..writeln(r''' done''')
..writeln(" cat <<'JSON'")
..writeln(
jsonEncode({
'id': 703,
'html_url': 'https://example.test/comment/703',
'body': 'posted',
}),
)
..writeln('JSON')
..writeln(' exit 0')
..writeln('fi');
buffer
..writeln(
'if [[ "\$1" == "api" && "\$4" == "repos/$repo/hooks/$webhookId" ]]; then',
)
..writeln(' exit 0')
..writeln('fi');
buffer
..writeln(r'''echo "unexpected gh args: $*" >&2''')
..writeln('exit 1');
await ghScript.writeAsString(buffer.toString());
await makeScriptExecutable(ghScript);
}