aa-structures 2.2.0__py3-none-any.whl → 2.3.1b1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {aa_structures-2.2.0.dist-info → aa_structures-2.3.1b1.dist-info}/METADATA +1 -1
- {aa_structures-2.2.0.dist-info → aa_structures-2.3.1b1.dist-info}/RECORD +28 -23
- {aa_structures-2.2.0.dist-info → aa_structures-2.3.1b1.dist-info}/WHEEL +1 -1
- structures/__init__.py +1 -1
- structures/admin.py +13 -13
- structures/core/notification_embeds.py +47 -47
- structures/locale/de/LC_MESSAGES/django.po +314 -305
- structures/locale/django.pot +2227 -0
- structures/locale/en/LC_MESSAGES/django.po +312 -305
- structures/locale/es/LC_MESSAGES/django.po +314 -305
- structures/locale/fr_FR/LC_MESSAGES/django.po +2227 -0
- structures/locale/it_IT/LC_MESSAGES/django.po +2227 -0
- structures/locale/ja/LC_MESSAGES/django.po +2228 -0
- structures/locale/ko_KR/LC_MESSAGES/django.po +2227 -0
- structures/locale/ru/LC_MESSAGES/django.po +312 -305
- structures/locale/uk/LC_MESSAGES/django.po +2231 -0
- structures/locale/zh_Hans/LC_MESSAGES/django.po +314 -305
- structures/managers.py +12 -12
- structures/models/notifications.py +20 -17
- structures/models/owners.py +1 -3
- structures/models/structures.py +1 -1
- structures/tests/core/test_notification_embeds.py +125 -36
- structures/tests/models/test_notifications_1.py +38 -12
- structures/tests/test_admin.py +48 -22
- structures/tests/test_integration.py +88 -1
- structures/tests/testdata/factories_2.py +34 -1
- structures/views.py +3 -2
- structures/tests/core/test_notification_embeds_2.py +0 -78
- {aa_structures-2.2.0.dist-info → aa_structures-2.3.1b1.dist-info}/licenses/LICENSE +0 -0
structures/tests/test_admin.py
CHANGED
@@ -11,15 +11,16 @@ from eveuniverse.models import EveEntity
|
|
11
11
|
|
12
12
|
from allianceauth.eveonline.models import EveCorporationInfo
|
13
13
|
|
14
|
-
from
|
14
|
+
from structures.admin import (
|
15
15
|
NotificationAdmin,
|
16
16
|
OwnerAdmin,
|
17
17
|
OwnerAllianceFilter,
|
18
18
|
OwnerCorporationsFilter,
|
19
19
|
StructureAdmin,
|
20
|
+
StructureFuelAlertConfigAdmin,
|
20
21
|
WebhookAdmin,
|
21
22
|
)
|
22
|
-
from
|
23
|
+
from structures.models import (
|
23
24
|
FuelAlertConfig,
|
24
25
|
Notification,
|
25
26
|
Owner,
|
@@ -27,6 +28,13 @@ from ..models import (
|
|
27
28
|
StructureTag,
|
28
29
|
Webhook,
|
29
30
|
)
|
31
|
+
|
32
|
+
from .testdata.factories_2 import (
|
33
|
+
FuelAlertConfigFactory,
|
34
|
+
NotificationFactory,
|
35
|
+
OwnerFactory,
|
36
|
+
StructureFactory,
|
37
|
+
)
|
30
38
|
from .testdata.helpers import (
|
31
39
|
create_structures,
|
32
40
|
create_user,
|
@@ -44,7 +52,7 @@ class MockRequest(object):
|
|
44
52
|
self.user = user
|
45
53
|
|
46
54
|
|
47
|
-
class
|
55
|
+
class TestFuelNotificationConfigAdminView(TestCase):
|
48
56
|
@classmethod
|
49
57
|
def setUpClass(cls):
|
50
58
|
super().setUpClass()
|
@@ -55,7 +63,6 @@ class TestFuelNotificationConfigAdmin(TestCase):
|
|
55
63
|
}
|
56
64
|
cls.user = User.objects.create_superuser("Clark Kent")
|
57
65
|
load_eveuniverse()
|
58
|
-
create_structures()
|
59
66
|
|
60
67
|
def test_should_create_new_config(self):
|
61
68
|
# given
|
@@ -74,7 +81,7 @@ class TestFuelNotificationConfigAdmin(TestCase):
|
|
74
81
|
def test_should_update_existing_config(self):
|
75
82
|
# given
|
76
83
|
self.client.force_login(self.user)
|
77
|
-
config =
|
84
|
+
config = FuelAlertConfigFactory(start=48, end=24, repeat=12)
|
78
85
|
# when
|
79
86
|
response = self.client.post(
|
80
87
|
reverse("admin:structures_fuelalertconfig_change", args=[config.pk]),
|
@@ -89,8 +96,8 @@ class TestFuelNotificationConfigAdmin(TestCase):
|
|
89
96
|
def test_should_remove_existing_fuel_notifications_when_timing_changed(self):
|
90
97
|
# given
|
91
98
|
self.client.force_login(self.user)
|
92
|
-
config =
|
93
|
-
structure =
|
99
|
+
config = FuelAlertConfigFactory(start=48, end=24, repeat=12)
|
100
|
+
structure = StructureFactory()
|
94
101
|
structure.structure_fuel_alerts.create(
|
95
102
|
config=config, structure=structure, hours=5
|
96
103
|
)
|
@@ -108,8 +115,8 @@ class TestFuelNotificationConfigAdmin(TestCase):
|
|
108
115
|
def test_should_not_remove_existing_fuel_notifications_on_other_changes(self):
|
109
116
|
# given
|
110
117
|
self.client.force_login(self.user)
|
111
|
-
config =
|
112
|
-
structure =
|
118
|
+
config = FuelAlertConfigFactory(start=48, end=24, repeat=12)
|
119
|
+
structure = StructureFactory()
|
113
120
|
structure.structure_fuel_alerts.create(
|
114
121
|
config=config, structure=structure, hours=5
|
115
122
|
)
|
@@ -156,7 +163,7 @@ class TestFuelNotificationConfigAdmin(TestCase):
|
|
156
163
|
def test_should_not_allow_creating_overlapping(self):
|
157
164
|
# given
|
158
165
|
self.client.force_login(self.user)
|
159
|
-
|
166
|
+
FuelAlertConfigFactory(start=48, end=24, repeat=12)
|
160
167
|
# when
|
161
168
|
response = self.client.post(
|
162
169
|
reverse("admin:structures_fuelalertconfig_add"),
|
@@ -207,6 +214,29 @@ class TestFuelNotificationConfigAdmin(TestCase):
|
|
207
214
|
self.assertEqual(FuelAlertConfig.objects.count(), 0)
|
208
215
|
|
209
216
|
|
217
|
+
class TestStructureFuelAlertAdmin(TestCase):
|
218
|
+
@classmethod
|
219
|
+
def setUpClass(cls):
|
220
|
+
super().setUpClass()
|
221
|
+
cls.modeladmin = StructureFuelAlertConfigAdmin(
|
222
|
+
model=FuelAlertConfig, admin_site=AdminSite()
|
223
|
+
)
|
224
|
+
load_eveuniverse()
|
225
|
+
|
226
|
+
@patch(MODULE_PATH + ".StructureFuelAlertConfigAdmin.message_user", spec=True)
|
227
|
+
@patch(MODULE_PATH + ".tasks", spec=True)
|
228
|
+
def test_should_send_fuel_notifications(self, mock_tasks, mock_message_user):
|
229
|
+
# given
|
230
|
+
config = FuelAlertConfigFactory()
|
231
|
+
request = MockRequest()
|
232
|
+
queryset = FuelAlertConfig.objects.filter(pk=config.pk)
|
233
|
+
# when
|
234
|
+
self.modeladmin.send_fuel_notifications(request, queryset)
|
235
|
+
# then
|
236
|
+
self.assertTrue(mock_tasks.send_queued_messages_for_webhooks.called)
|
237
|
+
self.assertTrue(mock_message_user.called)
|
238
|
+
|
239
|
+
|
210
240
|
class TestNotificationAdmin(TestCase):
|
211
241
|
@classmethod
|
212
242
|
def setUpClass(cls):
|
@@ -337,10 +367,10 @@ class TestOwnerAdmin(TestCase):
|
|
337
367
|
)
|
338
368
|
def test_should_return_correct_turnaround_times(self):
|
339
369
|
# given
|
340
|
-
my_owner =
|
370
|
+
my_owner = OwnerFactory()
|
341
371
|
my_sender = EveEntity.objects.get(id=1001)
|
342
372
|
my_now = now()
|
343
|
-
|
373
|
+
NotificationFactory(
|
344
374
|
owner=my_owner,
|
345
375
|
notification_id=1,
|
346
376
|
sender=my_sender,
|
@@ -350,7 +380,7 @@ class TestOwnerAdmin(TestCase):
|
|
350
380
|
)
|
351
381
|
for i in range(50):
|
352
382
|
timestamp = my_now + dt.timedelta(minutes=i)
|
353
|
-
|
383
|
+
NotificationFactory(
|
354
384
|
owner=my_owner,
|
355
385
|
notification_id=2 + i,
|
356
386
|
sender=my_sender,
|
@@ -422,12 +452,10 @@ class TestStructureAdmin(TestCase):
|
|
422
452
|
list_filter = (OwnerCorporationsFilter,)
|
423
453
|
|
424
454
|
Owner.objects.all().delete()
|
425
|
-
owner_2001 =
|
455
|
+
owner_2001 = OwnerFactory(
|
426
456
|
corporation=EveCorporationInfo.objects.get(corporation_id=2001)
|
427
457
|
)
|
428
|
-
|
429
|
-
corporation=EveCorporationInfo.objects.get(corporation_id=2002)
|
430
|
-
)
|
458
|
+
OwnerFactory(corporation=EveCorporationInfo.objects.get(corporation_id=2002))
|
431
459
|
my_modeladmin = StructureAdminTest(Structure, AdminSite())
|
432
460
|
|
433
461
|
# Make sure the lookups are correct
|
@@ -451,15 +479,13 @@ class TestStructureAdmin(TestCase):
|
|
451
479
|
list_filter = (OwnerAllianceFilter,)
|
452
480
|
|
453
481
|
Owner.objects.all().delete()
|
454
|
-
owner_2001 =
|
482
|
+
owner_2001 = OwnerFactory(
|
455
483
|
corporation=EveCorporationInfo.objects.get(corporation_id=2001)
|
456
484
|
)
|
457
|
-
owner_2002 =
|
485
|
+
owner_2002 = OwnerFactory(
|
458
486
|
corporation=EveCorporationInfo.objects.get(corporation_id=2002)
|
459
487
|
)
|
460
|
-
|
461
|
-
corporation=EveCorporationInfo.objects.get(corporation_id=2102)
|
462
|
-
)
|
488
|
+
OwnerFactory(corporation=EveCorporationInfo.objects.get(corporation_id=2102))
|
463
489
|
modeladmin = StructureAdminTest(Structure, AdminSite())
|
464
490
|
|
465
491
|
# Make sure the lookups are correct
|
@@ -16,9 +16,11 @@ from app_utils.esi_testing import EsiClientStub, EsiEndpoint
|
|
16
16
|
from .. import tasks
|
17
17
|
from ..models import NotificationType, Structure
|
18
18
|
from .testdata.factories_2 import (
|
19
|
+
EveEntityAllianceFactory,
|
19
20
|
EveEntityCorporationFactory,
|
20
21
|
NotificationFactory,
|
21
22
|
OwnerFactory,
|
23
|
+
RawNotificationFactory,
|
22
24
|
StarbaseFactory,
|
23
25
|
StructureFactory,
|
24
26
|
WebhookFactory,
|
@@ -431,6 +433,91 @@ class TestTasks(TestCase):
|
|
431
433
|
if AuthTimer:
|
432
434
|
self.assertTrue(AuthTimer.objects.exists())
|
433
435
|
|
436
|
+
def test_should_fetch_and_send_notification_when_enabled_for_webhook(
|
437
|
+
self, mock_esi_2, mock_esi, mock_execute
|
438
|
+
):
|
439
|
+
# given
|
440
|
+
webhook = WebhookFactory(
|
441
|
+
notification_types=[NotificationType.WAR_CORPORATION_BECAME_ELIGIBLE]
|
442
|
+
)
|
443
|
+
owner = OwnerFactory(webhooks=[webhook], is_alliance_main=True)
|
444
|
+
eve_character = owner.characters.first().character_ownership.character
|
445
|
+
# corporation_id = owner.corporation.corporation_id
|
446
|
+
notif = RawNotificationFactory()
|
447
|
+
endpoints = [
|
448
|
+
EsiEndpoint(
|
449
|
+
"Character",
|
450
|
+
"get_characters_character_id_notifications",
|
451
|
+
"character_id",
|
452
|
+
needs_token=True,
|
453
|
+
data={
|
454
|
+
str(eve_character.character_id): [notif],
|
455
|
+
},
|
456
|
+
),
|
457
|
+
]
|
458
|
+
mock_esi.client = mock_esi_2.client = EsiClientStub.create_from_endpoints(
|
459
|
+
endpoints
|
460
|
+
)
|
461
|
+
# when
|
462
|
+
tasks.fetch_all_notifications.delay()
|
463
|
+
# then
|
464
|
+
self.assertTrue(mock_execute.called)
|
465
|
+
embed = mock_execute.call_args[1]["embeds"][0]
|
466
|
+
self.assertIn("now eligible", embed.description)
|
467
|
+
|
468
|
+
def test_should_fetch_and_send_notification_when_enabled_for_webhook_all_anchoring(
|
469
|
+
self, mock_esi_2, mock_esi, mock_execute
|
470
|
+
):
|
471
|
+
# given
|
472
|
+
webhook = WebhookFactory(
|
473
|
+
notification_types=[NotificationType.SOV_ALL_ANCHORING_MSG]
|
474
|
+
)
|
475
|
+
owner = OwnerFactory(webhooks=[webhook], is_alliance_main=False)
|
476
|
+
eve_character = owner.characters.first().character_ownership.character
|
477
|
+
alliance = EveEntityAllianceFactory(
|
478
|
+
id=owner.corporation.alliance_id,
|
479
|
+
name=owner.corporation.alliance.alliance_name,
|
480
|
+
)
|
481
|
+
corporation = EveEntityCorporationFactory(
|
482
|
+
id=owner.corporation.corporation_id, name=owner.corporation.corporation_name
|
483
|
+
)
|
484
|
+
starbase = StarbaseFactory(owner=owner)
|
485
|
+
notif = RawNotificationFactory(
|
486
|
+
type="AllAnchoringMsg",
|
487
|
+
sender=corporation,
|
488
|
+
data={
|
489
|
+
"allianceID": alliance.id,
|
490
|
+
"corpID": corporation.id,
|
491
|
+
"corpsPresent": [{"allianceID": alliance.id, "corpID": corporation.id}],
|
492
|
+
"moonID": starbase.eve_moon.id,
|
493
|
+
"solarSystemID": starbase.eve_solar_system.id,
|
494
|
+
"towers": [
|
495
|
+
{"moonID": starbase.eve_moon.id, "typeID": starbase.eve_type.id}
|
496
|
+
],
|
497
|
+
"typeID": starbase.eve_type.id,
|
498
|
+
},
|
499
|
+
)
|
500
|
+
endpoints = [
|
501
|
+
EsiEndpoint(
|
502
|
+
"Character",
|
503
|
+
"get_characters_character_id_notifications",
|
504
|
+
"character_id",
|
505
|
+
needs_token=True,
|
506
|
+
data={
|
507
|
+
str(eve_character.character_id): [notif],
|
508
|
+
},
|
509
|
+
),
|
510
|
+
]
|
511
|
+
mock_esi.client = mock_esi_2.client = EsiClientStub.create_from_endpoints(
|
512
|
+
endpoints
|
513
|
+
)
|
514
|
+
# when
|
515
|
+
tasks.fetch_all_notifications.delay()
|
516
|
+
# then
|
517
|
+
self.assertTrue(mock_execute.called)
|
518
|
+
embed = mock_execute.call_args[1]["embeds"][0]
|
519
|
+
self.assertIn("has anchored in", embed.description)
|
520
|
+
|
434
521
|
@patch(NOTIFICATIONS_PATH + ".STRUCTURES_ADD_TIMERS", True)
|
435
522
|
def test_should_fetch_new_notification_from_esi_and_send_to_webhook_and_create_timers(
|
436
523
|
self, mock_esi_2, mock_esi, mock_execute
|
@@ -508,7 +595,7 @@ class TestTasks(TestCase):
|
|
508
595
|
NotificationType.SOV_ALL_CLAIM_LOST_MSG,
|
509
596
|
]
|
510
597
|
)
|
511
|
-
owner = OwnerFactory(webhooks=[webhook])
|
598
|
+
owner = OwnerFactory(webhooks=[webhook], is_alliance_main=True)
|
512
599
|
NotificationFactory(
|
513
600
|
owner=owner, notif_type=NotificationType.STRUCTURE_DESTROYED
|
514
601
|
)
|
@@ -142,7 +142,7 @@ class OwnerFactory(factory.django.DjangoModelFactory):
|
|
142
142
|
assets_last_update_at = factory.LazyFunction(now)
|
143
143
|
character_ownership = None # no longer used
|
144
144
|
forwarding_last_update_at = factory.LazyFunction(now)
|
145
|
-
is_alliance_main =
|
145
|
+
is_alliance_main = False
|
146
146
|
is_up = True
|
147
147
|
notifications_last_update_at = factory.LazyFunction(now)
|
148
148
|
structures_last_update_at = factory.LazyFunction(now)
|
@@ -330,3 +330,36 @@ class GeneratedNotificationFactory(factory.django.DjangoModelFactory):
|
|
330
330
|
state_timer_end=reinforced_until,
|
331
331
|
)
|
332
332
|
obj.structures.add(starbase)
|
333
|
+
|
334
|
+
|
335
|
+
class RawNotificationFactory(factory.DictFactory):
|
336
|
+
"""Create a raw notification as received from ESI."""
|
337
|
+
|
338
|
+
class Meta:
|
339
|
+
exclude = ("data", "timestamp_dt", "sender")
|
340
|
+
|
341
|
+
# excluded
|
342
|
+
data = None
|
343
|
+
timestamp_dt = None
|
344
|
+
sender = factory.SubFactory(EveEntityCorporationFactory, id=2902, name="CONCORD")
|
345
|
+
|
346
|
+
# included
|
347
|
+
notification_id = factory.Sequence(lambda o: 1999000000 + o)
|
348
|
+
type = "CorpBecameWarEligible"
|
349
|
+
sender_id = factory.LazyAttribute(lambda o: o.sender.id)
|
350
|
+
sender_type = factory.LazyAttribute(lambda o: o.sender.category)
|
351
|
+
is_read = True
|
352
|
+
|
353
|
+
@factory.lazy_attribute
|
354
|
+
def timestamp(self):
|
355
|
+
if not self.timestamp_dt:
|
356
|
+
timestamp_dt = now()
|
357
|
+
else:
|
358
|
+
timestamp_dt = self.timestamp_dt
|
359
|
+
return datetime_to_esi(timestamp_dt)
|
360
|
+
|
361
|
+
@factory.lazy_attribute
|
362
|
+
def text(self):
|
363
|
+
if not self.data:
|
364
|
+
return ""
|
365
|
+
return yaml.dump(self.data)
|
structures/views.py
CHANGED
@@ -1,6 +1,7 @@
|
|
1
1
|
import functools
|
2
2
|
from collections import defaultdict
|
3
3
|
from enum import IntEnum
|
4
|
+
from typing import Dict
|
4
5
|
from urllib.parse import urlencode
|
5
6
|
|
6
7
|
from django.contrib.auth.decorators import login_required, permission_required
|
@@ -354,7 +355,7 @@ def starbase_detail(request, structure_id):
|
|
354
355
|
assets = defaultdict(int)
|
355
356
|
for item in structure.items.select_related("eve_type"):
|
356
357
|
assets[item.eve_type_id] += item.quantity
|
357
|
-
eve_types = EveType.objects.in_bulk(id_list=assets.keys())
|
358
|
+
eve_types: Dict[int, EveType] = EveType.objects.in_bulk(id_list=assets.keys())
|
358
359
|
modules = sorted(
|
359
360
|
[
|
360
361
|
{"eve_type": eve_types.get(eve_type_id), "quantity": quantity}
|
@@ -432,7 +433,7 @@ def add_structure_owner(request, token):
|
|
432
433
|
owner.save()
|
433
434
|
|
434
435
|
if owner.characters.count() == 1:
|
435
|
-
tasks.update_all_for_owner.delay(owner_pk=owner.pk, user_pk=request.user.pk)
|
436
|
+
tasks.update_all_for_owner.delay(owner_pk=owner.pk, user_pk=request.user.pk) # type: ignore
|
436
437
|
messages_plus.info(
|
437
438
|
request,
|
438
439
|
format_html(
|
@@ -1,78 +0,0 @@
|
|
1
|
-
import dhooks_lite
|
2
|
-
|
3
|
-
from app_utils.testing import NoSocketsTestCase
|
4
|
-
|
5
|
-
from ...core.notification_embeds import (
|
6
|
-
NotificationBaseEmbed,
|
7
|
-
NotificationTowerReinforcedExtra,
|
8
|
-
)
|
9
|
-
from ...models import NotificationType
|
10
|
-
from ..testdata.factories_2 import (
|
11
|
-
EveEntityAllianceFactory,
|
12
|
-
GeneratedNotificationFactory,
|
13
|
-
NotificationFactory,
|
14
|
-
OwnerFactory,
|
15
|
-
)
|
16
|
-
from ..testdata.load_eveuniverse import load_eveuniverse
|
17
|
-
|
18
|
-
|
19
|
-
class TestGeneratedNotification(NoSocketsTestCase):
|
20
|
-
@classmethod
|
21
|
-
def setUpClass(cls):
|
22
|
-
super().setUpClass()
|
23
|
-
load_eveuniverse()
|
24
|
-
|
25
|
-
def test_should_create_tower_reinforced_embed(self):
|
26
|
-
# given
|
27
|
-
notif = GeneratedNotificationFactory()
|
28
|
-
# when
|
29
|
-
obj = NotificationBaseEmbed.create(notif)
|
30
|
-
# then
|
31
|
-
self.assertIsInstance(obj, NotificationTowerReinforcedExtra)
|
32
|
-
|
33
|
-
def test_should_generate_embed(self):
|
34
|
-
# given
|
35
|
-
notif = GeneratedNotificationFactory()
|
36
|
-
embed = NotificationBaseEmbed.create(notif)
|
37
|
-
# when
|
38
|
-
obj = embed.generate_embed()
|
39
|
-
# then
|
40
|
-
self.assertIsInstance(obj, dhooks_lite.Embed)
|
41
|
-
starbase = notif.structures.first()
|
42
|
-
self.assertIn(starbase.name, obj.description)
|
43
|
-
|
44
|
-
|
45
|
-
class TestEveNotificationEmbeds(NoSocketsTestCase):
|
46
|
-
@classmethod
|
47
|
-
def setUpClass(cls):
|
48
|
-
super().setUpClass()
|
49
|
-
load_eveuniverse()
|
50
|
-
cls.owner = OwnerFactory()
|
51
|
-
|
52
|
-
def test_should_create_sov_embed(self):
|
53
|
-
# given
|
54
|
-
notif = NotificationFactory(
|
55
|
-
owner=self.owner,
|
56
|
-
sender=EveEntityAllianceFactory(),
|
57
|
-
notif_type=NotificationType.SOV_ENTOSIS_CAPTURE_STARTED,
|
58
|
-
text_from_dict={"solarSystemID": 30000474, "structureTypeID": 32226},
|
59
|
-
)
|
60
|
-
embed = NotificationBaseEmbed.create(notif)
|
61
|
-
# when
|
62
|
-
obj = embed.generate_embed()
|
63
|
-
# then
|
64
|
-
self.assertIsInstance(obj, dhooks_lite.Embed)
|
65
|
-
|
66
|
-
def test_should_create_sov_embed_without_sender(self):
|
67
|
-
# given
|
68
|
-
notif = NotificationFactory(
|
69
|
-
owner=self.owner,
|
70
|
-
sender=None,
|
71
|
-
notif_type=NotificationType.SOV_ENTOSIS_CAPTURE_STARTED,
|
72
|
-
text_from_dict={"solarSystemID": 30000474, "structureTypeID": 32226},
|
73
|
-
)
|
74
|
-
embed = NotificationBaseEmbed.create(notif)
|
75
|
-
# when
|
76
|
-
obj = embed.generate_embed()
|
77
|
-
# then
|
78
|
-
self.assertIsInstance(obj, dhooks_lite.Embed)
|
File without changes
|