Skip to content

Commit 7bcad18

Browse files
Merge pull request #87 from Mr-Sunglasses/main
2 parents 5171641 + 7e57633 commit 7bcad18

File tree

2 files changed

+76
-186
lines changed

2 files changed

+76
-186
lines changed

src/paste/templates/paste.html

Lines changed: 65 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -189,7 +189,7 @@
189189
.copy-buttons-container {
190190
position: absolute;
191191
right: 20px;
192-
top: 20px;
192+
top: 70px;
193193
display: flex;
194194
flex-direction: column;
195195
gap: 10px;
@@ -305,7 +305,7 @@
305305

306306
.copy-buttons-container {
307307
right: 10px;
308-
top: 10px;
308+
top: 60px;
309309
}
310310

311311
.copy-button {
@@ -331,16 +331,15 @@
331331
<a href="{{ url_for('indexpage') }}" class="header-link">
332332
<div class="glitch-text" data-text="paste.py 🐍"><u>paste.py</u> 🐍</div>
333333
</a>
334-
<div style="width: 50px;"></div>
334+
<div class="copy-buttons-container">
335+
<button id="copyButton" class="copy-button" onclick="copyAllText()">
336+
<i class="fas fa-copy"></i> COPY CODE
337+
</button>
338+
<button id="copyLinkButton" class="copy-button" onclick="copyLink()">
339+
<i class="fas fa-link"></i> COPY LINK
340+
</button>
341+
</div>
335342
</div>
336-
<div class="copy-buttons-container">
337-
<button id="copyButton" class="copy-button" onclick="copyAllText()">
338-
<i class="fas fa-copy"></i> COPY CODE
339-
</button>
340-
<button id="copyLinkButton" class="copy-button" onclick="copyLink()">
341-
<i class="fas fa-link"></i> COPY LINK
342-
</button>
343-
</div>
344343
<div class="code">
345344
{{ highlighted_code | safe }}
346345
</div>
@@ -402,77 +401,75 @@
402401
}, 300);
403402
});
404403

405-
function copyAllText() {
406-
const codeElement = document.querySelector('.code pre');
407-
const range = document.createRange();
408-
range.selectNode(codeElement);
409-
410-
const selection = window.getSelection();
411-
selection.removeAllRanges();
412-
selection.addRange(range);
413-
414-
document.execCommand('copy');
415-
selection.removeAllRanges();
416-
417-
const copyButton = document.getElementById('copyButton');
418-
const originalText = copyButton.innerHTML;
419-
copyButton.innerHTML = '<i class="fas fa-check"></i> COPIED!';
420-
404+
function showCopyFeedback(button, success, successHTML, failureHTML) {
405+
const originalText = button.innerHTML;
406+
button.innerHTML = success ? successHTML : failureHTML;
421407
setTimeout(() => {
422-
copyButton.innerHTML = originalText;
408+
button.innerHTML = originalText;
423409
}, 2000);
424410
}
425-
426-
function copyLink() {
427-
const currentUrl = window.location.href;
428-
429-
if (navigator.clipboard) {
430-
navigator.clipboard.writeText(currentUrl).then(() => {
431-
const copyLinkButton = document.getElementById('copyLinkButton');
432-
const originalText = copyLinkButton.innerHTML;
433-
copyLinkButton.innerHTML = '<i class="fas fa-check"></i> LINK COPIED!';
434-
435-
setTimeout(() => {
436-
copyLinkButton.innerHTML = originalText;
437-
}, 2000);
438-
}).catch(() => {
439-
fallbackCopyLink(currentUrl);
440-
});
441-
} else {
442-
fallbackCopyLink(currentUrl);
443-
}
444-
}
445-
446-
function fallbackCopyLink(text) {
411+
412+
function fallbackCopy(textToCopy, button, successHTML, failureHTML) {
447413
const textArea = document.createElement("textarea");
448-
textArea.value = text;
414+
textArea.value = textToCopy;
449415
textArea.style.position = "fixed";
450416
textArea.style.left = "-999999px";
451417
textArea.style.top = "-999999px";
452418
document.body.appendChild(textArea);
453419
textArea.focus();
454420
textArea.select();
455-
421+
422+
let success = false;
456423
try {
457-
document.execCommand('copy');
458-
const copyLinkButton = document.getElementById('copyLinkButton');
459-
const originalText = copyLinkButton.innerHTML;
460-
copyLinkButton.innerHTML = '<i class="fas fa-check"></i> LINK COPIED!';
461-
462-
setTimeout(() => {
463-
copyLinkButton.innerHTML = originalText;
464-
}, 2000);
424+
success = document.execCommand('copy');
465425
} catch (err) {
466-
const copyLinkButton = document.getElementById('copyLinkButton');
467-
const originalText = copyLinkButton.innerHTML;
468-
copyLinkButton.innerHTML = '<i class="fas fa-times"></i> COPY FAILED';
469-
470-
setTimeout(() => {
471-
copyLinkButton.innerHTML = originalText;
472-
}, 2000);
426+
console.error('Fallback copy failed', err);
473427
}
474-
428+
475429
document.body.removeChild(textArea);
430+
showCopyFeedback(button, success, successHTML, failureHTML);
431+
}
432+
433+
function copyAllText() {
434+
const codeElement = document.querySelector('.code pre');
435+
if (!codeElement) return;
436+
437+
const clone = codeElement.cloneNode(true);
438+
const lineNumbers = clone.querySelectorAll('.linenos');
439+
lineNumbers.forEach(span => span.remove());
440+
const textToCopy = clone.textContent;
441+
442+
const copyButton = document.getElementById('copyButton');
443+
const successHTML = '<i class="fas fa-check"></i> COPIED!';
444+
const failureHTML = '<i class="fas fa-times"></i> FAILED!';
445+
446+
if (navigator.clipboard) {
447+
navigator.clipboard.writeText(textToCopy).then(() => {
448+
showCopyFeedback(copyButton, true, successHTML, failureHTML);
449+
}).catch(err => {
450+
console.error('Async copy failed, using fallback', err);
451+
fallbackCopy(textToCopy, copyButton, successHTML, failureHTML);
452+
});
453+
} else {
454+
fallbackCopy(textToCopy, copyButton, successHTML, failureHTML);
455+
}
456+
}
457+
458+
function copyLink() {
459+
const currentUrl = window.location.href;
460+
const copyLinkButton = document.getElementById('copyLinkButton');
461+
const successHTML = '<i class="fas fa-check"></i> LINK COPIED!';
462+
const failureHTML = '<i class="fas fa-times"></i> COPY FAILED';
463+
464+
if (navigator.clipboard) {
465+
navigator.clipboard.writeText(currentUrl).then(() => {
466+
showCopyFeedback(copyLinkButton, true, successHTML, failureHTML);
467+
}).catch(() => {
468+
fallbackCopy(currentUrl, copyLinkButton, successHTML, failureHTML);
469+
});
470+
} else {
471+
fallbackCopy(currentUrl, copyLinkButton, successHTML, failureHTML);
472+
}
476473
}
477474

478475
{% endblock %}

tests/test_api.py

Lines changed: 11 additions & 118 deletions
Original file line numberDiff line numberDiff line change
@@ -1,133 +1,26 @@
11
from fastapi.testclient import TestClient
22
from src.paste.main import app
33
from typing import Optional
4-
import os
54

65
client: TestClient = TestClient(app)
76

8-
file: Optional[str] = None
7+
paste_id: Optional[str] = None
98

109

1110
def test_get_health_route() -> None:
12-
data: dict = {"status": "ok"}
1311
response = client.get("/health")
1412
assert response.status_code == 200
15-
assert response.json() == data
1613

1714

18-
def test_get_homepage_route() -> None:
19-
response_expected_headers: str = "text/html; charset=utf-8"
20-
response = client.get("/")
21-
assert response.status_code == 200
22-
assert response.headers.get("Content-Type", "") == response_expected_headers
23-
24-
25-
def test_get_web_route() -> None:
26-
response_expected_headers: str = "text/html; charset=utf-8"
27-
response = client.get("/web")
28-
assert response.status_code == 200
29-
assert response.headers.get("Content-Type", "") == response_expected_headers
30-
31-
32-
def test_get_paste_data_route() -> None:
33-
data: str = "This is a test file."
34-
response = client.get("/paste/test")
35-
assert response.status_code == 200
36-
assert data in response.text
37-
38-
39-
def test_post_web_route() -> None:
40-
data: str = "This is a test data"
41-
form_data: dict = {"content": data, "extension": ".txt"}
42-
response = client.post("/web", data=form_data)
43-
global file
44-
file = str(response.url).split("/")[-1]
45-
assert response.status_code == 200
46-
assert data in response.text
47-
48-
49-
def test_delete_paste_route() -> None:
50-
expected_response: str = f"File successfully deleted {file}"
51-
response = client.delete(f"/paste/{file}")
52-
assert response.status_code == 200
53-
assert response.text == expected_response
54-
55-
56-
def test_post_file_route() -> None:
57-
response = client.post("/file", files={"file": ("test.txt", b"test file content")})
58-
assert response.status_code == 201
59-
response_file_uuid: str = response.text
60-
response = client.get(f"/paste/{response_file_uuid}")
61-
assert response.status_code == 200
62-
assert "test file content" in response.text
63-
response = client.delete(f"/paste/{response_file_uuid}")
64-
assert response.status_code == 200
65-
assert f"File successfully deleted {response_file_uuid}" in response.text
66-
67-
68-
def test_post_file_route_failure() -> None:
69-
response = client.post("/file")
70-
assert response.status_code == 422 # Unprocessable Entity
71-
# Add body assertion in future.
72-
73-
74-
def test_post_file_route_size_limit() -> None:
75-
large_file_name: str = "large_file.txt"
76-
file_size: int = 20 * 1024 * 1024 # 20 MB in bytes
77-
additional_bytes: int = 100 # Adding some extra bytes to exceed 20 MB
78-
content: bytes = b"This is a line in the file.\n"
79-
with open(large_file_name, "wb") as file:
80-
while file.tell() < file_size:
81-
file.write(content)
82-
file.write(b"Extra bytes to exceed 20 MB\n" * additional_bytes)
83-
file.close()
84-
f = open(large_file_name, "rb")
85-
files: dict = {"file": f}
86-
response = client.post("/file", files=files)
87-
f.close()
88-
# cleanup
89-
os.remove(large_file_name)
90-
assert response.status_code == 413
91-
assert "File is too large" in response.text
92-
93-
94-
def test_post_api_paste_route() -> None:
95-
paste_data = {"content": "This is a test paste content", "extension": "txt"}
96-
response = client.post("/api/paste", json=paste_data)
97-
assert response.status_code == 201
98-
response_json = response.json()
99-
assert "uuid" in response_json
100-
assert "url" in response_json
101-
assert response_json["uuid"].endswith(".txt")
102-
assert response_json["url"].startswith("http://paste.fosscu.org/paste/")
103-
104-
# Clean up: delete the created paste
105-
uuid = response_json["uuid"]
106-
delete_response = client.delete(f"/paste/{uuid}")
107-
assert delete_response.status_code == 200
108-
109-
110-
def test_get_api_paste_route() -> None:
111-
# First, create a paste
112-
paste_data = {"content": "This is a test paste content for GET", "extension": "md"}
113-
create_response = client.post("/api/paste", json=paste_data)
114-
assert create_response.status_code == 201
115-
created_uuid = create_response.json()["uuid"]
116-
117-
# Now, test getting the paste
118-
response = client.get(f"/api/paste/{created_uuid}")
119-
assert response.status_code == 200
120-
response_json = response.json()
121-
assert response_json["uuid"] == created_uuid
122-
assert response_json["content"] == paste_data["content"]
123-
assert response_json["extension"] == paste_data["extension"]
124-
125-
# Clean up: delete the created paste
126-
delete_response = client.delete(f"/paste/{created_uuid}")
127-
assert delete_response.status_code == 200
15+
def test_paste_api_route() -> None:
16+
respose = client.post(
17+
"/api/paste",
18+
json={
19+
"content": "Hello-World",
20+
},
21+
)
22+
paste_id = respose.text
23+
assert respose.status_code == 201
12824

12925

130-
def test_get_api_paste_route_not_found() -> None:
131-
response = client.get("/api/paste/nonexistent_uuid.txt")
132-
assert response.status_code == 404
133-
assert response.json()["detail"] == "Paste not found"
26+
print(paste_id)

0 commit comments

Comments
 (0)